brainsqueeze commited on
Commit
d9568eb
·
verified ·
1 Parent(s): 8ea16f9

Delete retrieval

Browse files
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&#61;{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