Spaces:
Running
Running
Delete retrieval
Browse files- retrieval/__init__.py +0 -0
- retrieval/candid_blog.py +0 -70
- retrieval/candid_help.py +0 -41
- retrieval/candid_learning.py +0 -41
- retrieval/config.py +0 -26
- retrieval/elastic.py +0 -320
- retrieval/elastic_qa.py +0 -194
- retrieval/issuelab.py +0 -50
- retrieval/youtube.py +0 -82
retrieval/__init__.py
DELETED
|
File without changes
|
retrieval/candid_blog.py
DELETED
|
@@ -1,70 +0,0 @@
|
|
| 1 |
-
from typing import Dict, Tuple, Any
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def build_wp_candid_blog_knn_model_query(
|
| 5 |
-
query: str,
|
| 6 |
-
fields: Tuple[str] = (
|
| 7 |
-
"content",
|
| 8 |
-
"title"
|
| 9 |
-
),
|
| 10 |
-
k: int = 10,
|
| 11 |
-
model_id: str = "sentence-transformers__all-mpnet-base-v2"
|
| 12 |
-
):
|
| 13 |
-
output = []
|
| 14 |
-
|
| 15 |
-
for f in fields:
|
| 16 |
-
output.append({
|
| 17 |
-
"field": f"embeddings.{f}.chunks.vector.predicted_value",
|
| 18 |
-
"k": k,
|
| 19 |
-
"num_candidates": 100,
|
| 20 |
-
"query_vector_builder": {
|
| 21 |
-
"text_embedding": {
|
| 22 |
-
"model_id": model_id,
|
| 23 |
-
"model_text": query
|
| 24 |
-
}
|
| 25 |
-
},
|
| 26 |
-
"boost": 1 / len(fields)
|
| 27 |
-
})
|
| 28 |
-
return {"knn": output}
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
def build_card_html(doc: Dict[str, Any], height_px: int = 200, show_chunks=False) -> str:
|
| 32 |
-
url = f"{doc['link']}"
|
| 33 |
-
fields = ["title", "excerpt"]
|
| 34 |
-
|
| 35 |
-
fields_dict = {}
|
| 36 |
-
fields_len = 0
|
| 37 |
-
for field in fields:
|
| 38 |
-
if doc.get(field, None) is not None:
|
| 39 |
-
fields_dict[field] = doc[field]
|
| 40 |
-
fields_dict[field + "_txt"] = f"<div>{doc[field]}</div>"
|
| 41 |
-
|
| 42 |
-
if (fields_len + len(doc[field])) > 999:
|
| 43 |
-
rest_text_len = 999 - fields_len
|
| 44 |
-
if rest_text_len > 0:
|
| 45 |
-
fields_dict[field + "_txt"] = f"<div>{doc[field][:rest_text_len] + '[...]'}</div>"
|
| 46 |
-
else: fields_dict[field + "_txt"] = f"<span>{'[...]'}</span>"
|
| 47 |
-
fields_len = fields_len + len(doc[field])
|
| 48 |
-
else:
|
| 49 |
-
fields_dict[field] = ""
|
| 50 |
-
fields_dict[field + "_txt"] = ""
|
| 51 |
-
html = f"""
|
| 52 |
-
<div style='height: {height_px}px; padding: 5px;'>
|
| 53 |
-
<div style='height: {height_px}px; border: 1px solid #febe10;'>
|
| 54 |
-
<span style='padding-left: 10px; display: inline-block; width: 100%;'>
|
| 55 |
-
<div>
|
| 56 |
-
<span>
|
| 57 |
-
<b>Candid blog post:</b>
|
| 58 |
-
<a href='{url}' target='_blank' style='text-decoration: none;'>
|
| 59 |
-
{doc['title']}
|
| 60 |
-
</a>
|
| 61 |
-
</span>
|
| 62 |
-
<br>
|
| 63 |
-
<br>
|
| 64 |
-
{fields_dict["excerpt_txt"]}
|
| 65 |
-
</div>
|
| 66 |
-
</span>
|
| 67 |
-
</div>
|
| 68 |
-
</div>
|
| 69 |
-
"""
|
| 70 |
-
return html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
retrieval/candid_help.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
| 1 |
-
from typing import Dict, Any
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def build_card_html(doc: Dict[str, Any], height_px: int = 200, show_chunks=False) -> str:
|
| 5 |
-
url = f"{doc['link']}"
|
| 6 |
-
fields = ["title", "summary"]
|
| 7 |
-
|
| 8 |
-
fields_dict = {}
|
| 9 |
-
fields_len = 0
|
| 10 |
-
for field in fields:
|
| 11 |
-
if doc.get(field, None) is not None:
|
| 12 |
-
fields_dict[field] = doc[field]
|
| 13 |
-
fields_dict[field + "_txt"] = f"<div>{doc[field]}</div>"
|
| 14 |
-
|
| 15 |
-
if (fields_len + len(doc[field])) > 999:
|
| 16 |
-
rest_text_len = 999 - fields_len
|
| 17 |
-
if rest_text_len > 0:
|
| 18 |
-
fields_dict[field + "_txt"] = f"<div>{doc[field][:rest_text_len] + '[...]'}</div>"
|
| 19 |
-
else: fields_dict[field + "_txt"] = f"<span>{'[...]'}</span>"
|
| 20 |
-
fields_len = fields_len + len(doc[field])
|
| 21 |
-
else:
|
| 22 |
-
fields_dict[field] = ""
|
| 23 |
-
fields_dict[field + "_txt"] = ""
|
| 24 |
-
html = f"""
|
| 25 |
-
<div style='height: {height_px}px; padding: 5px;'>
|
| 26 |
-
<div style='height: {height_px}px; border: 1px solid #febe10;'>
|
| 27 |
-
<span style='padding-left: 10px; display: inline-block; width: 100%;'>
|
| 28 |
-
<div>
|
| 29 |
-
<span>
|
| 30 |
-
<b>Candid help article:</b>
|
| 31 |
-
<a href='{url}' target='_blank' style='text-decoration: none;'>
|
| 32 |
-
{doc['title']}
|
| 33 |
-
</a>
|
| 34 |
-
</span>
|
| 35 |
-
<br>
|
| 36 |
-
</div>
|
| 37 |
-
</span>
|
| 38 |
-
</div>
|
| 39 |
-
</div>
|
| 40 |
-
"""
|
| 41 |
-
return html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
retrieval/candid_learning.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
| 1 |
-
from typing import Dict, Any
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def build_card_html(doc: Dict[str, Any], height_px: int = 200, show_chunks=False) -> str:
|
| 5 |
-
url = f"{doc['url']}"
|
| 6 |
-
fields = ["title", "excerpt"]
|
| 7 |
-
|
| 8 |
-
fields_dict = {}
|
| 9 |
-
fields_len = 0
|
| 10 |
-
for field in fields:
|
| 11 |
-
if doc.get(field, None) is not None:
|
| 12 |
-
fields_dict[field] = doc[field]
|
| 13 |
-
fields_dict[field + "_txt"] = f"<div>{doc[field]}</div>"
|
| 14 |
-
|
| 15 |
-
if (fields_len + len(doc[field])) > 999:
|
| 16 |
-
rest_text_len = 999 - fields_len
|
| 17 |
-
if rest_text_len > 0:
|
| 18 |
-
fields_dict[field + "_txt"] = f"<div>{doc[field][:rest_text_len] + '[...]'}</div>"
|
| 19 |
-
else: fields_dict[field + "_txt"] = f"<span>{'[...]'}</span>"
|
| 20 |
-
fields_len = fields_len + len(doc[field])
|
| 21 |
-
else:
|
| 22 |
-
fields_dict[field] = ""
|
| 23 |
-
fields_dict[field + "_txt"] = ""
|
| 24 |
-
html = f"""
|
| 25 |
-
<div style='height: {height_px}px; padding: 5px;'>
|
| 26 |
-
<div style='height: {height_px}px; border: 1px solid #febe10;'>
|
| 27 |
-
<span style='padding-left: 10px; display: inline-block; width: 100%;'>
|
| 28 |
-
<div>
|
| 29 |
-
<span>
|
| 30 |
-
<b>Candid Learning resource:</b>
|
| 31 |
-
<a href='{url}' target='_blank' style='text-decoration: none;'>
|
| 32 |
-
{doc['title']}
|
| 33 |
-
</a>
|
| 34 |
-
</span>
|
| 35 |
-
<br>
|
| 36 |
-
</div>
|
| 37 |
-
</span>
|
| 38 |
-
</div>
|
| 39 |
-
</div>
|
| 40 |
-
"""
|
| 41 |
-
return html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
retrieval/config.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
|
| 3 |
-
ONECANDID_QA = {
|
| 4 |
-
'ES_URL': os.getenv('ONECANDID_URL_QA'),
|
| 5 |
-
"ES_CLOUD_ID": os.getenv("ONECANDID_CLOUD_ID"),
|
| 6 |
-
"ES_API_KEY": os.getenv("ONECANDID_API_KEY")
|
| 7 |
-
}
|
| 8 |
-
|
| 9 |
-
class Indices:
|
| 10 |
-
ISSUELAB_INDEX = "search-semantic-issuelab_v1"
|
| 11 |
-
ISSUELAB_INDEX_ELSER = "search-semantic-issuelab-elser_ve2"
|
| 12 |
-
YOUTUBE_INDEX = "search-semantic-youtube_v1"
|
| 13 |
-
YOUTUBE_INDEX_ELSER = "search-semantic-youtube-elser_ve1"
|
| 14 |
-
CANDID_BLOG_INDEX = "search-semantic-candid-blog_v1"
|
| 15 |
-
CANDID_BLOG_INDEX_ELSER = "search-semantic-candid-blog-elser_ve2"
|
| 16 |
-
CANDID_LEARNING_INDEX_ELSER = "search-semantic-candid-learning_ve1"
|
| 17 |
-
CANDID_HELP_INDEX_ELSER = "search-semantic-candid-help-elser_ve1"
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
ALL_INDICES = (
|
| 21 |
-
"issuelab",
|
| 22 |
-
"youtube",
|
| 23 |
-
"candid_blog",
|
| 24 |
-
"candid_learning",
|
| 25 |
-
"candid_help"
|
| 26 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
retrieval/elastic.py
DELETED
|
@@ -1,320 +0,0 @@
|
|
| 1 |
-
from typing import List, Tuple, Dict, Iterable, Iterator, Optional, Any
|
| 2 |
-
from dataclasses import dataclass
|
| 3 |
-
from functools import partial
|
| 4 |
-
from itertools import groupby
|
| 5 |
-
|
| 6 |
-
from pydantic import BaseModel, Field
|
| 7 |
-
from langchain_core.documents import Document
|
| 8 |
-
from langchain_core.tools import Tool
|
| 9 |
-
|
| 10 |
-
from elasticsearch import Elasticsearch
|
| 11 |
-
|
| 12 |
-
try:
|
| 13 |
-
from config import ONECANDID_QA, ALL_INDICES, Indices
|
| 14 |
-
except ImportError:
|
| 15 |
-
from .config import ONECANDID_QA, ALL_INDICES, Indices
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
@dataclass
|
| 19 |
-
class ElasticHitsResult:
|
| 20 |
-
"""Dataclass for Elasticsearch hits results
|
| 21 |
-
"""
|
| 22 |
-
index: str
|
| 23 |
-
id: Any
|
| 24 |
-
score: float
|
| 25 |
-
source: Dict[str, Any]
|
| 26 |
-
inner_hits: Dict[str, Any]
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
class RetrieverInput(BaseModel):
|
| 30 |
-
"""Input to the Elasticsearch retriever."""
|
| 31 |
-
user_input: str = Field(description="query to look up in retriever")
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
def build_text_expansion_query(
|
| 35 |
-
query: str,
|
| 36 |
-
fields: Tuple[str],
|
| 37 |
-
model_id: str = ".elser_model_2_linux-x86_64"
|
| 38 |
-
) -> Dict[str, Any]:
|
| 39 |
-
|
| 40 |
-
output = []
|
| 41 |
-
|
| 42 |
-
for f in fields:
|
| 43 |
-
output.append({
|
| 44 |
-
"nested": {
|
| 45 |
-
"path": f"embeddings.{f}.chunks",
|
| 46 |
-
"query": {
|
| 47 |
-
"text_expansion": {
|
| 48 |
-
f"embeddings.{f}.chunks.vector": {
|
| 49 |
-
"model_id": model_id,
|
| 50 |
-
"model_text": query,
|
| 51 |
-
"boost": 1 / len(fields)
|
| 52 |
-
}
|
| 53 |
-
}
|
| 54 |
-
},
|
| 55 |
-
"inner_hits": {
|
| 56 |
-
"_source": False,
|
| 57 |
-
"size": 2,
|
| 58 |
-
"fields": [f"embeddings.{f}.chunks.chunk"]
|
| 59 |
-
}
|
| 60 |
-
}
|
| 61 |
-
})
|
| 62 |
-
return {"query": {"bool": {"should": output}}}
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
def query_builder(query: str, indices: List[str], **kwargs):
|
| 66 |
-
queries = []
|
| 67 |
-
if indices is None:
|
| 68 |
-
indices = list(ALL_INDICES)
|
| 69 |
-
|
| 70 |
-
for index in indices:
|
| 71 |
-
if index == "issuelab":
|
| 72 |
-
q = build_text_expansion_query(
|
| 73 |
-
query=query,
|
| 74 |
-
fields=("description", "content", "combined_issuelab_findings", "combined_item_description")
|
| 75 |
-
)
|
| 76 |
-
q["_source"] = {"excludes": ["embeddings"]}
|
| 77 |
-
q["size"] = 1
|
| 78 |
-
queries.extend([{"index": Indices.ISSUELAB_INDEX_ELSER}, q])
|
| 79 |
-
elif index == "youtube":
|
| 80 |
-
q = build_text_expansion_query(
|
| 81 |
-
query=query,
|
| 82 |
-
fields=("captions_cleaned", "description_cleaned", "title")
|
| 83 |
-
)
|
| 84 |
-
# text_cleaned duplicates captions_cleaned
|
| 85 |
-
q["_source"] = {"excludes": ["embeddings", "captions", "description", "text_cleaned"]}
|
| 86 |
-
q["size"] = 2
|
| 87 |
-
queries.extend([{"index": Indices.YOUTUBE_INDEX_ELSER}, q])
|
| 88 |
-
elif index == "candid_blog":
|
| 89 |
-
q = build_text_expansion_query(
|
| 90 |
-
query=query,
|
| 91 |
-
fields=("content", "title")
|
| 92 |
-
)
|
| 93 |
-
q["_source"] = {"excludes": ["embeddings"]}
|
| 94 |
-
q["size"] = 2
|
| 95 |
-
queries.extend([{"index": Indices.CANDID_BLOG_INDEX_ELSER}, q])
|
| 96 |
-
elif index == "candid_learning":
|
| 97 |
-
q = build_text_expansion_query(
|
| 98 |
-
query=query,
|
| 99 |
-
fields=("content", "title", "training_topics", "staff_recommendations")
|
| 100 |
-
)
|
| 101 |
-
q["_source"] = {"excludes": ["embeddings"]}
|
| 102 |
-
q["size"] = 2
|
| 103 |
-
queries.extend([{"index": Indices.CANDID_LEARNING_INDEX_ELSER}, q])
|
| 104 |
-
elif index == "candid_help":
|
| 105 |
-
q = build_text_expansion_query(
|
| 106 |
-
query=query,
|
| 107 |
-
fields=("content", "combined_article_description")
|
| 108 |
-
)
|
| 109 |
-
q["_source"] = {"excludes": ["embeddings"]}
|
| 110 |
-
q["size"] = 2
|
| 111 |
-
queries.extend([{"index": Indices.CANDID_HELP_INDEX_ELSER}, q])
|
| 112 |
-
|
| 113 |
-
return queries
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
def multi_search(queries: List[ElasticHitsResult]):
|
| 117 |
-
results = []
|
| 118 |
-
with Elasticsearch(
|
| 119 |
-
cloud_id=ONECANDID_QA["ES_CLOUD_ID"],
|
| 120 |
-
api_key=ONECANDID_QA["ES_API_KEY"],
|
| 121 |
-
verify_certs=False,
|
| 122 |
-
request_timeout=60 * 3
|
| 123 |
-
) as es:
|
| 124 |
-
for query_group in es.msearch(body=queries).get("responses", []):
|
| 125 |
-
for hit in query_group.get("hits", {}).get("hits", []):
|
| 126 |
-
hit = ElasticHitsResult(
|
| 127 |
-
index=hit["_index"],
|
| 128 |
-
id=hit["_id"],
|
| 129 |
-
score=hit["_score"],
|
| 130 |
-
source=hit["_source"],
|
| 131 |
-
inner_hits=hit.get("inner_hits", {})
|
| 132 |
-
)
|
| 133 |
-
results.append(hit)
|
| 134 |
-
return results
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
def get_query_results(search_text: str, indices: Optional[List[str]] = None):
|
| 138 |
-
queries = query_builder(query=search_text, indices=indices)
|
| 139 |
-
return multi_search(queries)
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
def reranker(query_results: Iterable[ElasticHitsResult]) -> Iterator[ElasticHitsResult]:
|
| 143 |
-
"""Reranks Elasticsearch hits coming from multiple indicies/queries which may have scores on different scales.
|
| 144 |
-
This will shuffle results
|
| 145 |
-
|
| 146 |
-
Parameters
|
| 147 |
-
----------
|
| 148 |
-
query_results : Iterable[ElasticHitsResult]
|
| 149 |
-
|
| 150 |
-
Yields
|
| 151 |
-
------
|
| 152 |
-
Iterator[ElasticHitsResult]
|
| 153 |
-
"""
|
| 154 |
-
|
| 155 |
-
results: List[ElasticHitsResult] = []
|
| 156 |
-
for _, data in groupby(query_results, key=lambda x: x.index):
|
| 157 |
-
data = list(data)
|
| 158 |
-
max_score = max(data, key=lambda x: x.score).score
|
| 159 |
-
min_score = min(data, key=lambda x: x.score).score
|
| 160 |
-
|
| 161 |
-
for d in data:
|
| 162 |
-
d.score = (d.score - min_score) / (max_score - min_score + 1e-9)
|
| 163 |
-
results.append(d)
|
| 164 |
-
|
| 165 |
-
yield from sorted(results, key=lambda x: x.score, reverse=True)
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
def get_results(user_input: str, indices: List[str]) -> List[ElasticHitsResult]:
|
| 169 |
-
output = ["Search didn't return any Candid sources"]
|
| 170 |
-
page_content=[]
|
| 171 |
-
content = "Search didn't return any Candid sources"
|
| 172 |
-
results = get_query_results(search_text=user_input, indices=indices)
|
| 173 |
-
if results:
|
| 174 |
-
output = get_reranked_results(results)
|
| 175 |
-
for doc in output:
|
| 176 |
-
page_content.append(doc.page_content)
|
| 177 |
-
content = "/n/n".join(page_content)
|
| 178 |
-
# for the tool we need to return a tuple for content_and_artifact type
|
| 179 |
-
return content, output
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
def get_context(field_name: str, hit: ElasticHitsResult, context_length: int = 1024) -> str:
|
| 183 |
-
"""Pads the relevant chunk of text with context before and after
|
| 184 |
-
|
| 185 |
-
Parameters
|
| 186 |
-
----------
|
| 187 |
-
field_name : str
|
| 188 |
-
a field with the long text that was chunked into pieces
|
| 189 |
-
hit : ElasticHitsResult
|
| 190 |
-
context_length : int, optional
|
| 191 |
-
length of text to add before and after the chunk, by default 1024
|
| 192 |
-
|
| 193 |
-
Returns
|
| 194 |
-
-------
|
| 195 |
-
str
|
| 196 |
-
longer chunks stuffed together
|
| 197 |
-
"""
|
| 198 |
-
|
| 199 |
-
chunks_with_context = []
|
| 200 |
-
long_text = hit.source.get(f"{field_name}", "")
|
| 201 |
-
inner_hits_field = f"embeddings.{field_name}.chunks"
|
| 202 |
-
inner_hits = hit.inner_hits
|
| 203 |
-
found_chunks = inner_hits.get(inner_hits_field, {})
|
| 204 |
-
if found_chunks:
|
| 205 |
-
hits = found_chunks.get("hits", {}).get("hits", [])
|
| 206 |
-
for h in hits:
|
| 207 |
-
chunk = h.get("fields", {})[inner_hits_field][0]["chunk"][0]
|
| 208 |
-
chunk = chunk[3:-3] # cutting the middle because we may have tokenizing artefacts there
|
| 209 |
-
# Find the start and end indices of the chunk in the large text
|
| 210 |
-
start_index = long_text.find(chunk)
|
| 211 |
-
if start_index != -1: # Chunk is found
|
| 212 |
-
end_index = start_index + len(chunk)
|
| 213 |
-
pre_start_index = max(0, start_index - context_length)
|
| 214 |
-
post_end_index = min(len(long_text), end_index + context_length)
|
| 215 |
-
context = long_text[pre_start_index:post_end_index]
|
| 216 |
-
chunks_with_context.append(context)
|
| 217 |
-
chunks_with_context_txt = '\n\n'.join(chunks_with_context)
|
| 218 |
-
|
| 219 |
-
return chunks_with_context_txt
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
def process_hit(hit: ElasticHitsResult) -> Document | None:
|
| 223 |
-
if "issuelab-elser" in hit.index:
|
| 224 |
-
combined_item_description = hit.source.get("combined_item_description", "") # title inside
|
| 225 |
-
description = hit.source.get("description", "")
|
| 226 |
-
combined_issuelab_findings = hit.source.get("combined_issuelab_findings", "")
|
| 227 |
-
# we only need to process long texts
|
| 228 |
-
chunks_with_context_txt = get_context("content", hit, context_length=12)
|
| 229 |
-
doc = Document(
|
| 230 |
-
page_content='\n\n'.join([
|
| 231 |
-
combined_item_description,
|
| 232 |
-
combined_issuelab_findings,
|
| 233 |
-
description,
|
| 234 |
-
chunks_with_context_txt
|
| 235 |
-
]),
|
| 236 |
-
metadata={
|
| 237 |
-
"source": "IssueLab",
|
| 238 |
-
"source_id": hit.source["resource_id"],
|
| 239 |
-
"url": hit.source.get("permalink", "")
|
| 240 |
-
}
|
| 241 |
-
)
|
| 242 |
-
elif "youtube" in hit.index:
|
| 243 |
-
title = hit.source.get("title", "")
|
| 244 |
-
# we only need to process long texts
|
| 245 |
-
description_cleaned_with_context_txt = get_context("description_cleaned", hit, context_length=12)
|
| 246 |
-
captions_cleaned_with_context_txt = get_context("captions_cleaned", hit, context_length=12)
|
| 247 |
-
doc = Document(
|
| 248 |
-
page_content='\n\n'.join([title, description_cleaned_with_context_txt, captions_cleaned_with_context_txt]),
|
| 249 |
-
metadata={
|
| 250 |
-
"source": "Candid Youtube",
|
| 251 |
-
"source_id": hit.source['video_id'],
|
| 252 |
-
"url": f"https://www.youtube.com/watch?v={hit.source['video_id']}"
|
| 253 |
-
}
|
| 254 |
-
)
|
| 255 |
-
elif "candid-blog" in hit.index:
|
| 256 |
-
excerpt = hit.source.get("excerpt", "")
|
| 257 |
-
title = hit.source.get("title", "")
|
| 258 |
-
# we only need to process long texts
|
| 259 |
-
content_with_context_txt = get_context("content", hit, context_length=12)
|
| 260 |
-
doc = Document(
|
| 261 |
-
page_content='\n\n'.join([title, excerpt, content_with_context_txt]),
|
| 262 |
-
metadata={
|
| 263 |
-
"source": "Candid Blog",
|
| 264 |
-
"source_id": hit.source["id"],
|
| 265 |
-
"url": hit.source["link"]
|
| 266 |
-
}
|
| 267 |
-
)
|
| 268 |
-
elif "candid-learning" in hit.index:
|
| 269 |
-
title = hit.source.get("title", "")
|
| 270 |
-
content_with_context_txt = get_context("content", hit, context_length=12)
|
| 271 |
-
training_topics = hit.source.get("training_topics", "")
|
| 272 |
-
staff_recommendations = hit.source.get("staff_recommendations", "")
|
| 273 |
-
|
| 274 |
-
doc = Document(
|
| 275 |
-
page_content='\n\n'.join([title, staff_recommendations, training_topics, content_with_context_txt]),
|
| 276 |
-
metadata={
|
| 277 |
-
"source": "Candid Learning",
|
| 278 |
-
"source_id": hit.source["post_id"],
|
| 279 |
-
"url": hit.source.get("url", "")
|
| 280 |
-
}
|
| 281 |
-
)
|
| 282 |
-
elif "candid-help" in hit.index:
|
| 283 |
-
title = hit.source.get("title", "")
|
| 284 |
-
content_with_context_txt = get_context("content", hit, context_length=12)
|
| 285 |
-
combined_article_description = hit.source.get("combined_article_description", "")
|
| 286 |
-
|
| 287 |
-
doc = Document(
|
| 288 |
-
page_content='\n\n'.join([combined_article_description, content_with_context_txt]),
|
| 289 |
-
metadata={
|
| 290 |
-
"source": "Candid Help",
|
| 291 |
-
"source_id": hit.source["id"],
|
| 292 |
-
"url": hit.source.get("link", "")
|
| 293 |
-
}
|
| 294 |
-
)
|
| 295 |
-
else:
|
| 296 |
-
doc = None
|
| 297 |
-
return doc
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
def get_reranked_results(results: List[ElasticHitsResult]) -> List[Document]:
|
| 301 |
-
output = []
|
| 302 |
-
for r in reranker(results):
|
| 303 |
-
hit = process_hit(r)
|
| 304 |
-
output.append(hit)
|
| 305 |
-
return output
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
def retriever_tool(indices: List[str]) -> Tool:
|
| 309 |
-
# cannot use create_retriever_tool because it only provides content losing all metadata on the way
|
| 310 |
-
# https://python.langchain.com/docs/how_to/custom_tools/#returning-artifacts-of-tool-execution
|
| 311 |
-
return Tool(
|
| 312 |
-
name="retrieve_social_sector_information",
|
| 313 |
-
func=partial(get_results, indices=indices),
|
| 314 |
-
description=(
|
| 315 |
-
"Return additional information about social and philanthropic sector, "
|
| 316 |
-
"including nonprofits (NGO), grants, foundations, funding, RFP, LOI, Candid."
|
| 317 |
-
),
|
| 318 |
-
args_schema=RetrieverInput,
|
| 319 |
-
response_format="content_and_artifact"
|
| 320 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
retrieval/elastic_qa.py
DELETED
|
@@ -1,194 +0,0 @@
|
|
| 1 |
-
from typing import List, Dict, Any, Optional
|
| 2 |
-
import re
|
| 3 |
-
import os
|
| 4 |
-
|
| 5 |
-
import numpy as np
|
| 6 |
-
|
| 7 |
-
from langchain_huggingface.embeddings import HuggingFaceEndpointEmbeddings
|
| 8 |
-
from langchain_core.documents import Document
|
| 9 |
-
|
| 10 |
-
from ...socialspark.retrieval_qa.elastic import ElasticHitsResult, ElasticsearchQABase
|
| 11 |
-
|
| 12 |
-
from .elastic import query_builder
|
| 13 |
-
from .config import ALL_INDICES
|
| 14 |
-
|
| 15 |
-
timestamp_pattern = re.compile(r"^(\d:\d{2}:\d{2}\.\d{3}),(\d:\d{2}:\d{2}\.\d{3})\n(.*)$")
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
def parse_youtube_captions(caption_text: str) -> List[str]:
|
| 19 |
-
timestamp_blocks = re.split(r'\n{2}', caption_text)
|
| 20 |
-
parsed_blocks = [m.groups() for block in timestamp_blocks if (m:=timestamp_pattern.match(block)) if not None]
|
| 21 |
-
video_length_mins = int(parsed_blocks[-1][0].split(':')[1])
|
| 22 |
-
|
| 23 |
-
# what is the block timing in minutes which gives 10 chunks? keep minimum 1 minute breaks to maintain context
|
| 24 |
-
minute_difference = min(video_length_mins // 10, 1)
|
| 25 |
-
|
| 26 |
-
prev_minute_start = 0
|
| 27 |
-
texts = []
|
| 28 |
-
text_block = []
|
| 29 |
-
for start, _, text in parsed_blocks:
|
| 30 |
-
text_block.append(text)
|
| 31 |
-
|
| 32 |
-
current_minute = int(start.split(':')[1])
|
| 33 |
-
if (current_minute - prev_minute_start) >= minute_difference:
|
| 34 |
-
texts.append(' '.join(text_block))
|
| 35 |
-
text_block.clear()
|
| 36 |
-
prev_minute_start = current_minute
|
| 37 |
-
|
| 38 |
-
return texts
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
def parse_candid_learning(text: str) -> List[str]:
|
| 42 |
-
texts = []
|
| 43 |
-
for block in map(str.strip, re.split(r'\n{1,}', text, flags=re.I | re.M)):
|
| 44 |
-
if (
|
| 45 |
-
'back to top' in block.lower()
|
| 46 |
-
or 'table of contents' in block.lower()
|
| 47 |
-
):
|
| 48 |
-
continue
|
| 49 |
-
|
| 50 |
-
texts.append(block)
|
| 51 |
-
return texts
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
class ElasticsearchQA(ElasticsearchQABase):
|
| 55 |
-
|
| 56 |
-
indices: Optional[List[str]] = ALL_INDICES
|
| 57 |
-
embedding: Any = HuggingFaceEndpointEmbeddings(
|
| 58 |
-
huggingfacehub_api_token=os.getenv("HF_API_KEY"),
|
| 59 |
-
# model="sentence-transformers/all-mpnet-base-v2"
|
| 60 |
-
model="mixedbread-ai/mxbai-embed-large-v1"
|
| 61 |
-
)
|
| 62 |
-
|
| 63 |
-
def build_query(self, query: str, **kwargs) -> List[Dict[str, Any]]:
|
| 64 |
-
queries = query_builder(query=query, indices=self.indices)
|
| 65 |
-
return queries
|
| 66 |
-
|
| 67 |
-
def sub_section_alignment(self, query: str, document: List[str]) -> str:
|
| 68 |
-
question_vector = np.array(self.embedding.embed_query(query), dtype='float32')
|
| 69 |
-
vectors = np.array(self.embedding.embed_documents(document), dtype='float32')
|
| 70 |
-
vectors = np.array(vectors, dtype='float32')
|
| 71 |
-
|
| 72 |
-
size = max(5, int(0.1 * len(document)))
|
| 73 |
-
|
| 74 |
-
doc_vector = vectors.sum(axis=0, keepdims=True)
|
| 75 |
-
vectors /= np.linalg.norm(vectors, ord=2.0, axis=-1, keepdims=True)
|
| 76 |
-
doc_vector /= np.linalg.norm(doc_vector, ord=2.0, axis=-1, keepdims=True)
|
| 77 |
-
question_vector /= np.linalg.norm(question_vector, ord=2.0, axis=-1, keepdims=True)
|
| 78 |
-
# similarity = (doc_vector * vectors).sum(axis=-1)
|
| 79 |
-
similarity = (question_vector * vectors).sum(axis=-1)
|
| 80 |
-
|
| 81 |
-
return '\n'.join(text for text, _ in sorted(zip(document, similarity), key=lambda x: 1 - x[-1])[:size])
|
| 82 |
-
|
| 83 |
-
def process_hit(self, hit: ElasticHitsResult, q: str) -> Document | None:
|
| 84 |
-
if "news" in hit.index:
|
| 85 |
-
doc = Document(
|
| 86 |
-
page_content='\n\n'.join(v for k, v in hit.source["texts"].items() if v),
|
| 87 |
-
metadata={
|
| 88 |
-
"source": "news",
|
| 89 |
-
"source_id": hit.source['metadata']['link']
|
| 90 |
-
}
|
| 91 |
-
)
|
| 92 |
-
elif "transactions" in hit.index:
|
| 93 |
-
doc = Document(
|
| 94 |
-
page_content='\n\n'.join(v for k, v in hit.source["semantic_texts"].items() if v),
|
| 95 |
-
metadata={
|
| 96 |
-
"source": "cds-transactions",
|
| 97 |
-
"source_id": hit.source["id"]
|
| 98 |
-
}
|
| 99 |
-
)
|
| 100 |
-
elif "organizations" in hit.index:
|
| 101 |
-
source = hit.source
|
| 102 |
-
org_gen = source.get("combined_organization_description_general", "")
|
| 103 |
-
org_fin = source.get("combined_organization_description_financial", "")
|
| 104 |
-
org_contact = source.get("combined_organization_description_contacts", "")
|
| 105 |
-
mission = ""
|
| 106 |
-
if source.get("mission_statement", None) is not None:
|
| 107 |
-
mission = source.get("mission_statement", "")
|
| 108 |
-
keyword = ""
|
| 109 |
-
if source.get("keyword", None) is not None:
|
| 110 |
-
keyword = source.get("keyword", "")
|
| 111 |
-
programs = source.get("programs", "")
|
| 112 |
-
doc = Document(
|
| 113 |
-
page_content='\n\n'.join([org_gen, mission, keyword, org_fin, org_contact, programs]),
|
| 114 |
-
metadata={
|
| 115 |
-
"source": "UP-organizations-QA",
|
| 116 |
-
"source_id": hit.source["candid_entity_id"]
|
| 117 |
-
}
|
| 118 |
-
)
|
| 119 |
-
# elif "issuelab" in hit.index:
|
| 120 |
-
# doc = Document(
|
| 121 |
-
# page_content='\n\n'.join(v for k, v in hit.source["semantic_texts"].items() if v),
|
| 122 |
-
# metadata={
|
| 123 |
-
# "source": "IssueLab",
|
| 124 |
-
# "source_id": hit.source["resource_id"]
|
| 125 |
-
# }
|
| 126 |
-
# )
|
| 127 |
-
elif "issuelab-elser" in hit.index:
|
| 128 |
-
title = hit.source.get("title", "")
|
| 129 |
-
description = hit.source.get("description", "")
|
| 130 |
-
doc = Document(
|
| 131 |
-
page_content='\n\n'.join([title, description]),
|
| 132 |
-
metadata={
|
| 133 |
-
"source": "IssueLab",
|
| 134 |
-
"source_id": hit.source["resource_id"],
|
| 135 |
-
"url": hit.source.get("permalink", "")
|
| 136 |
-
}
|
| 137 |
-
)
|
| 138 |
-
elif "youtube" in hit.index:
|
| 139 |
-
title = hit.source.get("title", "")
|
| 140 |
-
summary = self.sub_section_alignment(
|
| 141 |
-
query=q,
|
| 142 |
-
document=parse_youtube_captions(hit.source.get("text"))
|
| 143 |
-
)
|
| 144 |
-
doc = Document(
|
| 145 |
-
# page_content='\n\n'.join([title]),
|
| 146 |
-
page_content=summary,
|
| 147 |
-
metadata={
|
| 148 |
-
"source": "Candid's Youtube channel",
|
| 149 |
-
"source_id": hit.source['video_id'],
|
| 150 |
-
"url": f"https://www.youtube.com/watch?v={hit.source['video_id']}"
|
| 151 |
-
}
|
| 152 |
-
)
|
| 153 |
-
elif "candid-blog" in hit.index:
|
| 154 |
-
excerpt = hit.source.get("excerpt", "")
|
| 155 |
-
title = hit.source.get("title", "")
|
| 156 |
-
doc = Document(
|
| 157 |
-
page_content='\n\n'.join([title, excerpt]),
|
| 158 |
-
metadata={
|
| 159 |
-
"source": "Candid Blog",
|
| 160 |
-
"source_id": hit.source["id"],
|
| 161 |
-
"url": hit.source["link"]
|
| 162 |
-
}
|
| 163 |
-
)
|
| 164 |
-
elif "candid-learning" in hit.index:
|
| 165 |
-
# content = hit.source.get("content", "")
|
| 166 |
-
title = hit.source.get("title", "")
|
| 167 |
-
summary = self.sub_section_alignment(
|
| 168 |
-
query=q,
|
| 169 |
-
document=parse_candid_learning(hit.source.get("content", ""))
|
| 170 |
-
)
|
| 171 |
-
doc = Document(
|
| 172 |
-
# page_content='\n\n'.join([title]),
|
| 173 |
-
page_content=summary,
|
| 174 |
-
metadata={
|
| 175 |
-
"source": "Candid Learning",
|
| 176 |
-
"source_id": hit.source["post_id"],
|
| 177 |
-
"url": hit.source.get("url", "")
|
| 178 |
-
}
|
| 179 |
-
)
|
| 180 |
-
elif "candid-help" in hit.index:
|
| 181 |
-
title = hit.source.get("title", "")
|
| 182 |
-
content = hit.source.get("content", "")
|
| 183 |
-
|
| 184 |
-
doc = Document(
|
| 185 |
-
page_content='\n\n'.join([title, content]),
|
| 186 |
-
metadata={
|
| 187 |
-
"source": "Candid Help",
|
| 188 |
-
"source_id": hit.source["id"],
|
| 189 |
-
"url": hit.source.get("link", "")
|
| 190 |
-
}
|
| 191 |
-
)
|
| 192 |
-
else:
|
| 193 |
-
doc = None
|
| 194 |
-
return doc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
retrieval/issuelab.py
DELETED
|
@@ -1,50 +0,0 @@
|
|
| 1 |
-
from typing import Dict, Any
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def issuelab_card_html(doc: Dict[str, Any], height_px: int = 200, show_chunks=False) -> str:
|
| 5 |
-
chunks_html = ""
|
| 6 |
-
if show_chunks:
|
| 7 |
-
cleaned_text = []
|
| 8 |
-
for k, v in doc["inner_hits"].items():
|
| 9 |
-
hits = v["hits"]["hits"]
|
| 10 |
-
for h in hits:
|
| 11 |
-
for k1, v1 in h["fields"].items():
|
| 12 |
-
# we don't want other chunks
|
| 13 |
-
if "content" in k1:
|
| 14 |
-
cleaned_text.append(f"<div><p>{v1[0]['chunk'][0]}</p></div>")
|
| 15 |
-
|
| 16 |
-
chunks_html ="<span><b>Relevant parts of the content:</b></span>" + "<br>".join(cleaned_text)
|
| 17 |
-
|
| 18 |
-
html = f"""
|
| 19 |
-
<div style='height: auto; padding: 5px;'>
|
| 20 |
-
<div style='border: 1px solid #febe10;'>
|
| 21 |
-
<span style='display: inline-block; height: {height_px - 10}px; padding: 5px; vertical-align: top;'>
|
| 22 |
-
<img
|
| 23 |
-
src='{doc['cover_graphic_small']}'
|
| 24 |
-
style='max-height: 100%; overflow: hidden; border-radius: 3%;'
|
| 25 |
-
>
|
| 26 |
-
</span>
|
| 27 |
-
|
| 28 |
-
<span style='padding: 10px; display: inline-block; width: 70%;'>
|
| 29 |
-
<div>
|
| 30 |
-
<span><b>Issuelab ID:</b> {doc['resource_id']}</span>
|
| 31 |
-
<br>
|
| 32 |
-
<span>
|
| 33 |
-
<a href='{doc['issuelab_url']}' target='_blank' style='text-decoration: none;'>
|
| 34 |
-
{doc['title']}
|
| 35 |
-
</a>
|
| 36 |
-
</span>
|
| 37 |
-
<br>
|
| 38 |
-
|
| 39 |
-
<span><b>Description:</b> {doc['description']}</span>
|
| 40 |
-
<br>
|
| 41 |
-
<div>{doc['combined_item_description']}</div>
|
| 42 |
-
<br>
|
| 43 |
-
<div>{chunks_html}</div>
|
| 44 |
-
|
| 45 |
-
</div>
|
| 46 |
-
</span>
|
| 47 |
-
</div>
|
| 48 |
-
</div>
|
| 49 |
-
"""
|
| 50 |
-
return html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
retrieval/youtube.py
DELETED
|
@@ -1,82 +0,0 @@
|
|
| 1 |
-
from typing import Dict, Tuple, Any
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def build_knn_model_query(
|
| 5 |
-
query: str,
|
| 6 |
-
fields: Tuple[str] = (
|
| 7 |
-
"captions_cleaned",
|
| 8 |
-
"description_cleaned",
|
| 9 |
-
"title"
|
| 10 |
-
),
|
| 11 |
-
k: int = 10,
|
| 12 |
-
model_id: str = "sentence-transformers__all-mpnet-base-v2"
|
| 13 |
-
):
|
| 14 |
-
output = []
|
| 15 |
-
|
| 16 |
-
for f in fields:
|
| 17 |
-
output.append({
|
| 18 |
-
"field": f"embeddings.{f}.chunks.vector.predicted_value",
|
| 19 |
-
"k": k,
|
| 20 |
-
"num_candidates": 100,
|
| 21 |
-
"query_vector_builder": {
|
| 22 |
-
"text_embedding": {
|
| 23 |
-
"model_id": model_id,
|
| 24 |
-
"model_text": query
|
| 25 |
-
}
|
| 26 |
-
},
|
| 27 |
-
"boost": 1 / len(fields)
|
| 28 |
-
})
|
| 29 |
-
return {"knn": output}
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def build_card_html(doc: Dict[str, Any], height_px: int = 200, show_chunks=False) -> str:
|
| 33 |
-
url = f"https://www.youtube.com/watch?v={doc['video_id']}"
|
| 34 |
-
fields = ["title", "description_cleaned"]
|
| 35 |
-
|
| 36 |
-
fields_dict = {}
|
| 37 |
-
fields_len = 0
|
| 38 |
-
for field in fields:
|
| 39 |
-
if doc.get(field, None) is not None:
|
| 40 |
-
fields_dict[field] = doc[field]
|
| 41 |
-
fields_dict[field + "_txt"] = f"<div>{doc[field]}</div>"
|
| 42 |
-
|
| 43 |
-
if (fields_len + len(doc[field])) > 999:
|
| 44 |
-
rest_text_len = 999 - fields_len
|
| 45 |
-
if rest_text_len > 0:
|
| 46 |
-
fields_dict[field + "_txt"] = f"<div>{doc[field][:rest_text_len] + '[...]'}</div>"
|
| 47 |
-
else: fields_dict[field + "_txt"] = f"<span>{'[...]'}</span>"
|
| 48 |
-
fields_len = fields_len + len(doc[field])
|
| 49 |
-
else:
|
| 50 |
-
fields_dict[field] = ""
|
| 51 |
-
fields_dict[field + "_txt"] = ""
|
| 52 |
-
html = f"""
|
| 53 |
-
<div style='height: {height_px}px; padding: 5px;'>
|
| 54 |
-
<div style='height: {height_px}px; border: 1px solid #febe10;'>
|
| 55 |
-
<span style='padding-left: 10px; display: inline-block; width: 100%;'>
|
| 56 |
-
<div>
|
| 57 |
-
<span>
|
| 58 |
-
<b>Candid Youtube video:</b>
|
| 59 |
-
<a href='{url}' target='_blank' style='text-decoration: none;'>
|
| 60 |
-
{doc['title']}
|
| 61 |
-
</a>
|
| 62 |
-
</span>
|
| 63 |
-
<iframe
|
| 64 |
-
width="426"
|
| 65 |
-
height="240"
|
| 66 |
-
src="https://www.youtube.com/embed/{doc['video_id']}?si=0-y6eRrOzXTUSBDY"
|
| 67 |
-
title="YouTube video player"
|
| 68 |
-
frameborder="0"
|
| 69 |
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
| 70 |
-
referrerpolicy="strict-origin-when-cross-origin"
|
| 71 |
-
allowfullscreen
|
| 72 |
-
style="display: inline-block; float: left;padding-right: 10px;padding-top: 5px;">
|
| 73 |
-
</iframe>
|
| 74 |
-
<br>
|
| 75 |
-
<br>
|
| 76 |
-
{fields_dict["description_cleaned_txt"]}
|
| 77 |
-
</div>
|
| 78 |
-
</span>
|
| 79 |
-
</div>
|
| 80 |
-
</div>
|
| 81 |
-
"""
|
| 82 |
-
return html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|