jiarongqiu commited on
Commit
3fdc088
·
1 Parent(s): e68950c
Files changed (4) hide show
  1. service/api.py +13 -2
  2. service/crawler.py +100 -0
  3. service/vector_store.py +50 -6
  4. util/schema.py +79 -1
service/api.py CHANGED
@@ -15,10 +15,22 @@ class API():
15
  def __init__(self) -> None:
16
  pass
17
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  def get_suggestion(self,query):
19
  if not query:
20
  return []
21
- docs = vector_store.marginal_search(query)
22
  res = []
23
  for doc in docs:
24
  name = doc.metadata.get('title_llm','')
@@ -28,7 +40,6 @@ class API():
28
  res.append({"name":name,"url":url})
29
  # logger.write_log({"query":query,"api":'auto_complete',"result":res})
30
  return res
31
-
32
 
33
  def get_answer(self,query):
34
  docs = vector_store.search(query)
 
15
  def __init__(self) -> None:
16
  pass
17
 
18
+ def search(self,query,**kwargs):
19
+ if not query:
20
+ return []
21
+ docs = vector_store.search(query,**kwargs)
22
+ return docs
23
+
24
+ def marginal_search(self,query,**kwargs):
25
+ if not query:
26
+ return []
27
+ docs = vector_store.marginal_search(query,**kwargs)
28
+ return docs
29
+
30
  def get_suggestion(self,query):
31
  if not query:
32
  return []
33
+ docs = vector_store.marginal_search(query,lambda_mult=0.6)
34
  res = []
35
  for doc in docs:
36
  name = doc.metadata.get('title_llm','')
 
40
  res.append({"name":name,"url":url})
41
  # logger.write_log({"query":query,"api":'auto_complete',"result":res})
42
  return res
 
43
 
44
  def get_answer(self,query):
45
  docs = vector_store.search(query)
service/crawler.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ from tqdm import tqdm
4
+ from bs4 import BeautifulSoup
5
+ from langchain.document_loaders.recursive_url_loader import RecursiveUrlLoader
6
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
7
+ from typing import List
8
+ from service.llm import Bot
9
+ from util.schema import MyDoc
10
+ from typing import List
11
+
12
+ class Crawler(RecursiveUrlLoader):
13
+
14
+ HEADERS = {
15
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36'
16
+ }
17
+ CHUNK_SIZE = 2000
18
+ TEMPLATE = """
19
+ You are an expert in refining and summarization based on the title, description of the document to make it more concise and informative. Please generate a refined title, description, and keywords for the document in json format.
20
+ Remember, the generated keywords should be less than 3 words, the generated title should be less than 10 words, and the generated description should be less than 100 words.
21
+ Here is a legal example of the json format:
22
+ {{
23
+ "title": "The Title of the Document",
24
+ "description": "The Description of the Document",
25
+ "keywords": ["keyword1", "keyword2", "keyword3"]
26
+ }}
27
+ """
28
+
29
+ def __init__(self,keywords=[]):
30
+ self.keywords = keywords
31
+ self.splitter = RecursiveCharacterTextSplitter(chunk_size=self.CHUNK_SIZE, chunk_overlap=200)
32
+ self.bot = Bot(template=self.TEMPLATE)
33
+ super().__init__(
34
+ url="",
35
+ extractor=self._extractor,
36
+ timeout=600,
37
+ headers=self.HEADERS,
38
+ exclude_dirs=[],
39
+ prevent_outside=True
40
+ )
41
+
42
+ def _extractor(self,html: str) -> str:
43
+ text = BeautifulSoup(html, "lxml").get_text(" ")
44
+ text = re.sub(r"\n\n+", "\n\n", text)
45
+ text = re.sub(r"\s\s+", " ", text).strip()
46
+ if self.check_keywords(text):
47
+ return text
48
+ return
49
+
50
+ def check_keywords(self,text):
51
+ if not self.keywords:
52
+ return True
53
+ for key in self.keywords:
54
+ if key in text:
55
+ return True
56
+ return False
57
+
58
+ def __call__(self,url,visited=set(),max_depth=2,prevent_outside=True):
59
+ self.url = url
60
+ self.max_depth = max_depth
61
+ self.prevent_outside = prevent_outside
62
+ docs = list(self._get_child_links_recursive(url, visited))
63
+ return docs
64
+
65
+ def from_docs(self,docs):
66
+ return MyDoc.from_docs(docs)
67
+
68
+ def chunk(self,docs):
69
+ res = []
70
+ for doc in docs:
71
+ res += self.splitter.split_documents([doc])
72
+ return res
73
+
74
+ def llm_augment(self,docs:List[MyDoc]):
75
+ question_template = """
76
+ Please generate a refined title, description, and keywords for the document
77
+ 1. title:{title}
78
+ 2. desription:{description}
79
+ 3. content:{content}
80
+ """
81
+ res =[]
82
+ for doc in tqdm(docs):
83
+ question = question_template.format(
84
+ title=doc.title,
85
+ description=doc.description,
86
+ content=doc.page_content
87
+ )
88
+ response = self.bot.custom_call(question=question)
89
+ try:
90
+ response = response.strip('`').strip('json\n')
91
+ data = json.loads(response)
92
+ doc.keywords = data['keywords']
93
+ doc.description_llm = data['description']
94
+ doc.title_llm = data['title']
95
+ res.append(doc)
96
+ except Exception as e:
97
+ print(response)
98
+ return res
99
+
100
+ crawler = Crawler()
service/vector_store.py CHANGED
@@ -38,13 +38,57 @@ class VectorStore(Pinecone):
38
  pinecone.create_index(name=self.INDEX_NAME, metric="cosine", dimension=self.dims)
39
  Pinecone.from_documents(docs, self.embeddings, index_name=self.INDEX_NAME)
40
 
41
- # @timing
42
- def search(self,query):
43
- return self.similarity_search(query)
 
 
 
 
 
44
 
45
- # @timing
46
- def marginal_search(self,query,k=5):
47
- return self.max_marginal_relevance_search(query,k=k)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  def jsonfy(self,docs):
50
  docs = [doc.dict() for doc in docs]
 
38
  pinecone.create_index(name=self.INDEX_NAME, metric="cosine", dimension=self.dims)
39
  Pinecone.from_documents(docs, self.embeddings, index_name=self.INDEX_NAME)
40
 
41
+ def search(self,
42
+ query: str,
43
+ k: int = 4,
44
+ filter: Optional[dict] = None,
45
+ namespace: Optional[str] = None,
46
+ **kwargs: Any,
47
+ ) -> List[Document]:
48
+ """Return pinecone documents most similar to query.
49
 
50
+ Args:
51
+ query: Text to look up documents similar to.
52
+ k: Number of Documents to return. Defaults to 4.
53
+ filter: Dictionary of argument(s) to filter on metadata
54
+ namespace: Namespace to search in. Default will search in '' namespace.
55
+
56
+ Returns:
57
+ List of Documents most similar to the query and score for each
58
+ """
59
+ docs_and_scores = self.similarity_search_with_score(
60
+ query, k=k, filter=filter, namespace=namespace, **kwargs
61
+ )
62
+ return [doc for doc, _ in docs_and_scores]
63
+
64
+ def marginal_search(self,
65
+ query: str,
66
+ k: int = 4,
67
+ fetch_k: int = 20,
68
+ lambda_mult: float = 0.5,
69
+ filter: Optional[dict] = None,
70
+ namespace: Optional[str] = None,
71
+ **kwargs: Any,
72
+ ) -> List[Document]:
73
+ """Return docs selected using the maximal marginal relevance.
74
+
75
+ Maximal marginal relevance optimizes for similarity to query AND diversity
76
+ among selected documents.
77
+
78
+ Args:
79
+ query: Text to look up documents similar to.
80
+ k: Number of Documents to return. Defaults to 4.
81
+ fetch_k: Number of Documents to fetch to pass to MMR algorithm.
82
+ lambda_mult: Number between 0 and 1 that determines the degree of diversity among the results with 0 corresponding
83
+ to maximum diversity and 1 to minimum diversity.
84
+ Defaults to 0.5.
85
+ Returns:
86
+ List of Documents selected by maximal marginal relevance.
87
+ """
88
+ embedding = self._embed_query(query)
89
+ return self.max_marginal_relevance_search_by_vector(
90
+ embedding, k, fetch_k, lambda_mult, filter, namespace
91
+ )
92
 
93
  def jsonfy(self,docs):
94
  docs = [doc.dict() for doc in docs]
util/schema.py CHANGED
@@ -39,4 +39,82 @@ class Card:
39
  "title": self.title,
40
  "description": self.description,
41
  "url": self.url
42
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  "title": self.title,
40
  "description": self.description,
41
  "url": self.url
42
+ }
43
+
44
+
45
+
46
+ class MyDoc:
47
+
48
+ def __init__(self, content, metadata):
49
+ self._content = content
50
+ self._metadata = {k:v for k,v in metadata.items() if v is not None and v.strip()!= ''}
51
+
52
+ @staticmethod
53
+ def from_docs(docs):
54
+ res = []
55
+ for doc in docs:
56
+ res.append(MyDoc(doc.page_content, doc.metadata))
57
+ return res
58
+
59
+ @property
60
+ def idx(self):
61
+ return self.metadata.get('idx', -1)
62
+
63
+ @property
64
+ def page_content(self):
65
+ return self._content
66
+
67
+ @property
68
+ def metadata(self):
69
+ return self._metadata
70
+
71
+ @page_content.setter
72
+ def page_content(self, value):
73
+ self._page_content = value
74
+
75
+ @metadata.setter
76
+ def metadata(self, value):
77
+ self._metadata = value
78
+
79
+ @property
80
+ def title(self):
81
+ return self.metadata.get('title', '')
82
+
83
+ @property
84
+ def source(self):
85
+ return self.metadata.get('source', '')
86
+
87
+ @property
88
+ def description(self):
89
+ return self.metadata.get('description', '')
90
+
91
+ @property
92
+ def language(self):
93
+ return self.metadata.get('language', '')
94
+
95
+ @property
96
+ def keywords(self):
97
+ return self.metadata.get('keywords', [])
98
+
99
+ @property
100
+ def description_llm(self):
101
+ return self.metadata.get('description_llm', '')
102
+
103
+ @property
104
+ def title_llm(self):
105
+ return self.metadata.get('title_llm', '')
106
+
107
+ @description_llm.setter
108
+ def description_llm(self, value):
109
+ self.metadata['description_llm'] = value
110
+
111
+ @title_llm.setter
112
+ def title_llm(self, value):
113
+ self.metadata['title_llm'] = value
114
+
115
+ @keywords.setter
116
+ def keywords(self, value):
117
+ self.metadata['keywords'] = value
118
+
119
+ def __str__(self):
120
+ return f"Source: {self.source}"