esemsc-am4224 commited on
Commit
277590a
·
1 Parent(s): 20ec0c0

feat: added indexing configuration

Browse files
data/agentbase.csv CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:f8ac2555a8cacd01b1e319abe638b5da638451b67cd247350acd3fe46e71599e
3
- size 13923827
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8e02bc6fc96153821bccb4ebd290a9ba848bbf50a0e05e9d21e4f7f1b46c05dc
3
+ size 13917037
data/agentbase_mini.csv CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:cc54651a1cb6d275ea43b663e324efc3181bd0dfd2d9eb1df04dff81cd6c9ff9
3
- size 439127
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f13942b04f3f6ae6f8eafdc7eb35d73f725288c39939bfb13af42ef789d1197c
3
+ size 438503
data/embeddings/{BAAI_bge-large-en-v1.5_88f95605539a0823.npz → BAAI_bge-large-en-v1.5_18d58ad4370dc14d.npz} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:75f3cfb24da3dba8c17590bbe7d59958f2bcc31d6cb6547e20d791d045453393
3
- size 37193250
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8204e3f290f8f7b2e1ce2fd42728e82ebe2c5d9b4eb73eb2a6578d6efe0e6411
3
+ size 37193225
data/embeddings/BAAI_bge-large-en-v1.5_8c6dc4a78211c0a6.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f1e757ca5e98cf0243dc147e093f1b4ca663eb7dc3b03cc6cffc5d5f1c503da2
3
+ size 37191771
data/embeddings/{BAAI_bge-large-en-v1.5_6380768bd184d185.npz → BAAI_bge-large-en-v1.5_fb62913d86e36ab8.npz} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:cffc46d72e08a7acf60b72a587896bc581f5129ecba306f059db31b435b4cdb3
3
- size 1144547
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7c7a12acaacfac522fccf7db985f6d105f13427caeecff1cec3133742edb3ed0
3
+ size 1144274
interface.py CHANGED
@@ -15,7 +15,7 @@ from retrieval.models.sentence_bert import DenseRetriever
15
  from retrieval.utils import load_queries
16
 
17
 
18
- def keyword_filter(df, query, columns, top_k=100) -> List[Tuple[str, float]]:
19
  """
20
  Simple keyword-based boolean filter across specified columns.
21
  """
@@ -39,11 +39,22 @@ class AgentBaseUI:
39
  self.platforms_df = pd.read_csv(platforms_path)
40
 
41
  # initialise retrievers
42
- self.retrieval_models = ["bm25", "bge-large-en-v1.5", "keyword"]
43
  self.selected_model = "bge-large-en-v1.5"
44
- self.columns = ["agent_name", "agent_description"] # experimental
45
- self.bm25 = BM25Retriever(agentbase_path, columns=self.columns)
46
- self.bge = DenseRetriever("BAAI/bge-large-en-v1.5", agentbase_path, columns=self.columns)
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  def show_header(self):
49
  st.set_page_config(page_title="AgentBase", layout="wide")
@@ -59,15 +70,13 @@ class AgentBaseUI:
59
  if suggestion_cols[i].button(suggestion):
60
  st.session_state.query = suggestion
61
 
62
- col1, col2 = st.columns([3, 1])
63
  with col1:
64
  st.session_state.query = st.text_input("", placeholder="Type to search...", value=st.session_state.query)
65
  with col2:
66
- self.selected_model = st.selectbox(
67
- "",
68
- self.retrieval_models,
69
- index=0
70
- )
71
 
72
  def show_agents(self):
73
  st.header("Agents")
@@ -110,11 +119,11 @@ class AgentBaseUI:
110
  Default maximum top_k of 100
111
  """
112
  if self.selected_model == 'keyword':
113
- return keyword_filter(self.agents_df, query, self.columns, top_k)
114
  elif self.selected_model == 'bm25':
115
- res = self.bm25.retrieve(query, top_k)
116
  elif self.selected_model == 'bge-large-en-v1.5':
117
- res = self.bge.retrieve(query, top_k)
118
  else:
119
  raise ValueError(f"Selected model must be one of {self.retrieval_models}")
120
 
 
15
  from retrieval.utils import load_queries
16
 
17
 
18
+ def keyword_filter(df, query, columns=["agent_name", "agent_description"], top_k=100) -> List[Tuple[str, float]]:
19
  """
20
  Simple keyword-based boolean filter across specified columns.
21
  """
 
39
  self.platforms_df = pd.read_csv(platforms_path)
40
 
41
  # initialise retrievers
42
+ self.retrieval_models = ["bge-large-en-v1.5", "bm25", "keyword"]
43
  self.selected_model = "bge-large-en-v1.5"
44
+ self.indexing_configs = ["v1", "naive"]
45
+ self.indexing_config = "v1" # default
46
+
47
+ self.visible_configs = ["indexing1", "indexing2"]
48
+ self.visible_configs_to_indexing = {
49
+ "indexing1": "v1",
50
+ "indexing2": "naive"
51
+ }
52
+
53
+ self.bm25s = {}
54
+ self.bges = {}
55
+ for idx_config in self.indexing_configs:
56
+ self.bm25s[idx_config] = BM25Retriever(agentbase_path, index_config=idx_config)
57
+ self.bges[idx_config] = DenseRetriever("BAAI/bge-large-en-v1.5", agentbase_path, index_config=idx_config)
58
 
59
  def show_header(self):
60
  st.set_page_config(page_title="AgentBase", layout="wide")
 
70
  if suggestion_cols[i].button(suggestion):
71
  st.session_state.query = suggestion
72
 
73
+ col1, col2, col3 = st.columns([4, 1, 1])
74
  with col1:
75
  st.session_state.query = st.text_input("", placeholder="Type to search...", value=st.session_state.query)
76
  with col2:
77
+ self.selected_model = st.selectbox("", self.retrieval_models, index=0)
78
+ with col3:
79
+ self.indexing_config = st.selectbox("", self.visible_configs, index=0)
 
 
80
 
81
  def show_agents(self):
82
  st.header("Agents")
 
119
  Default maximum top_k of 100
120
  """
121
  if self.selected_model == 'keyword':
122
+ return keyword_filter(self.agents_df, query, top_k)
123
  elif self.selected_model == 'bm25':
124
+ res = self.bm25s[self.visible_configs_to_indexing[self.indexing_config]].retrieve(query, top_k)
125
  elif self.selected_model == 'bge-large-en-v1.5':
126
+ res = self.bges[self.visible_configs_to_indexing[self.indexing_config]].retrieve(query, top_k)
127
  else:
128
  raise ValueError(f"Selected model must be one of {self.retrieval_models}")
129
 
retrieval/base.py CHANGED
@@ -1,5 +1,8 @@
1
  from abc import ABC, abstractmethod
2
- from typing import List, Tuple, Dict
 
 
 
3
 
4
 
5
  class BaseRetriever(ABC):
@@ -7,12 +10,16 @@ class BaseRetriever(ABC):
7
  Abstract base class for AgentBase retrievers.
8
  """
9
 
10
- def __init__(self, db_path: str, columns: List[str]):
11
  self.db_path = db_path
12
- self.columns = columns
13
  self.agent_ids = []
14
  self.documents = []
15
-
 
 
 
 
 
16
  @abstractmethod
17
  def build_index(self) -> None:
18
  """Build retrieval index from database."""
 
1
  from abc import ABC, abstractmethod
2
+ from typing import List, Tuple, Dict, Literal
3
+ from functools import partial
4
+
5
+ from .utils import load_documents, agentbase_indexing
6
 
7
 
8
  class BaseRetriever(ABC):
 
10
  Abstract base class for AgentBase retrievers.
11
  """
12
 
13
+ def __init__(self, db_path: str, index_config: Literal["naive", "v1"]):
14
  self.db_path = db_path
 
15
  self.agent_ids = []
16
  self.documents = []
17
+ self.index_config = index_config
18
+ self.indexing_func = {
19
+ "naive": partial(load_documents, self.db_path),
20
+ "v1": partial(agentbase_indexing, self.db_path),
21
+ }
22
+
23
  @abstractmethod
24
  def build_index(self) -> None:
25
  """Build retrieval index from database."""
retrieval/models/bm25.py CHANGED
@@ -1,17 +1,17 @@
1
- from typing import List, Tuple, Dict
2
 
3
  import numpy as np
4
  from rank_bm25 import BM25Okapi
5
 
6
  from ..base import BaseRetriever
7
- from ..utils import load_documents, tokenise
8
 
9
 
10
  class BM25Retriever(BaseRetriever):
11
  """BM25 sparse retrieval for AgentBase."""
12
 
13
- def __init__(self, db_path: str, columns: List[str], **bm25_params):
14
- super().__init__(db_path, columns)
15
  self.bm25_params = bm25_params
16
  self.index = None
17
  self.build_index()
@@ -20,7 +20,7 @@ class BM25Retriever(BaseRetriever):
20
  """
21
  Load documents and build BM25 index.
22
  """
23
- self.agent_ids, self.documents = load_documents(self.db_path, self.columns)
24
  tokenised_docs = [tokenise(doc) for doc in self.documents]
25
  self.index = BM25Okapi(tokenised_docs, **self.bm25_params)
26
 
@@ -30,6 +30,5 @@ class BM25Retriever(BaseRetriever):
30
  """
31
  tokenized_query = tokenise(query)
32
  scores = self.index.get_scores(tokenized_query)
33
-
34
  top_indices = np.argsort(scores)[-top_k:][::-1]
35
  return [(self.agent_ids[idx], float(scores[idx])) for idx in top_indices]
 
1
+ from typing import List, Tuple, Dict, Literal
2
 
3
  import numpy as np
4
  from rank_bm25 import BM25Okapi
5
 
6
  from ..base import BaseRetriever
7
+ from ..utils import tokenise
8
 
9
 
10
  class BM25Retriever(BaseRetriever):
11
  """BM25 sparse retrieval for AgentBase."""
12
 
13
+ def __init__(self, db_path: str, index_config: Literal["naive", "v1"], **bm25_params):
14
+ super().__init__(db_path, index_config)
15
  self.bm25_params = bm25_params
16
  self.index = None
17
  self.build_index()
 
20
  """
21
  Load documents and build BM25 index.
22
  """
23
+ self.agent_ids, self.documents = self.indexing_func[self.index_config]()
24
  tokenised_docs = [tokenise(doc) for doc in self.documents]
25
  self.index = BM25Okapi(tokenised_docs, **self.bm25_params)
26
 
 
30
  """
31
  tokenized_query = tokenise(query)
32
  scores = self.index.get_scores(tokenized_query)
 
33
  top_indices = np.argsort(scores)[-top_k:][::-1]
34
  return [(self.agent_ids[idx], float(scores[idx])) for idx in top_indices]
retrieval/models/sentence_bert.py CHANGED
@@ -1,20 +1,20 @@
1
- from typing import List, Tuple, Dict
2
  from pathlib import Path
3
  import hashlib
4
  import json
5
 
6
  import numpy as np
 
7
  from sentence_transformers import SentenceTransformer
8
 
9
  from ..base import BaseRetriever
10
- from ..utils import load_documents
11
 
12
 
13
  class DenseRetriever(BaseRetriever):
14
  """Dense retrieval using sentence transformers."""
15
 
16
- def __init__(self, model_name: str, db_path: str, columns: List[str]):
17
- super().__init__(db_path, columns)
18
  self.model_name = model_name
19
  self.model = SentenceTransformer(model_name)
20
  self.corpus_embeddings = None
@@ -23,11 +23,10 @@ class DenseRetriever(BaseRetriever):
23
 
24
  def _default_embeddings_path(self) -> str:
25
  model_safe = self.model_name.replace("/", "_")
26
- agent_ids, _ = load_documents(self.db_path, self.columns)
27
  payload = {
28
  "model": self.model_name,
29
- "columns": self.columns,
30
- "rows": len(agent_ids)
31
  }
32
  # stable SHA-256 hash based on payload
33
  hash_str = hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()[:16]
@@ -44,7 +43,7 @@ class DenseRetriever(BaseRetriever):
44
  embeddings=self.corpus_embeddings,
45
  agent_ids=np.array(self.agent_ids),
46
  model_name=self.model_name,
47
- columns=self.columns
48
  )
49
 
50
  def load_index(self):
@@ -57,17 +56,17 @@ class DenseRetriever(BaseRetriever):
57
 
58
  # verify metadata
59
  stored_model = str(data['model_name'])
60
- stored_cols = data['columns'].tolist()
61
  if stored_model != self.model_name:
62
  print(f"WARNING: Loaded embeddings from {stored_model}, but using {self.model_name}")
63
- if stored_cols != self.columns:
64
- raise ValueError(f"Column mismatch! Stored: {stored_cols}, Expected: {self.columns}")
65
 
66
  def build_index(self):
67
  """
68
  Build your embeddings.
69
  """
70
- self.agent_ids, self.corpus = load_documents(self.db_path, self.columns)
71
  self.corpus_embeddings = self.model.encode(
72
  self.corpus,
73
  show_progress_bar=True,
@@ -77,8 +76,9 @@ class DenseRetriever(BaseRetriever):
77
 
78
  def retrieve(self, query: str, top_k: int = 10):
79
  """
80
- Retrieve using cosine similarity.
81
  """
 
82
  query_embedding = self.model.encode([query], convert_to_numpy=True)[0]
83
  scores = np.dot(self.corpus_embeddings, query_embedding)
84
  top_indices = np.argsort(scores)[-top_k:][::-1]
 
1
+ from typing import List, Tuple, Dict, Literal
2
  from pathlib import Path
3
  import hashlib
4
  import json
5
 
6
  import numpy as np
7
+ import pandas as pd
8
  from sentence_transformers import SentenceTransformer
9
 
10
  from ..base import BaseRetriever
 
11
 
12
 
13
  class DenseRetriever(BaseRetriever):
14
  """Dense retrieval using sentence transformers."""
15
 
16
+ def __init__(self, model_name: str, db_path: str, index_config: Literal["naive", "v1"]):
17
+ super().__init__(db_path, index_config)
18
  self.model_name = model_name
19
  self.model = SentenceTransformer(model_name)
20
  self.corpus_embeddings = None
 
23
 
24
  def _default_embeddings_path(self) -> str:
25
  model_safe = self.model_name.replace("/", "_")
 
26
  payload = {
27
  "model": self.model_name,
28
+ "columns": self.index_config,
29
+ "rows": len(pd.read_csv(self.db_path))
30
  }
31
  # stable SHA-256 hash based on payload
32
  hash_str = hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()[:16]
 
43
  embeddings=self.corpus_embeddings,
44
  agent_ids=np.array(self.agent_ids),
45
  model_name=self.model_name,
46
+ index_config=self.index_config
47
  )
48
 
49
  def load_index(self):
 
56
 
57
  # verify metadata
58
  stored_model = str(data['model_name'])
59
+ stored_index_config = data['index_config'].tolist()
60
  if stored_model != self.model_name:
61
  print(f"WARNING: Loaded embeddings from {stored_model}, but using {self.model_name}")
62
+ if stored_index_config != self.index_config:
63
+ raise ValueError(f"Index configuration mismatch! Stored: {stored_index_config}, Expected: {self.index_config}")
64
 
65
  def build_index(self):
66
  """
67
  Build your embeddings.
68
  """
69
+ self.agent_ids, self.corpus = self.indexing_func[self.index_config]()
70
  self.corpus_embeddings = self.model.encode(
71
  self.corpus,
72
  show_progress_bar=True,
 
76
 
77
  def retrieve(self, query: str, top_k: int = 10):
78
  """
79
+ Retrieve using dot product.
80
  """
81
+ # NOTE: .encode() internally normalises our vectors
82
  query_embedding = self.model.encode([query], convert_to_numpy=True)[0]
83
  scores = np.dot(self.corpus_embeddings, query_embedding)
84
  top_indices = np.argsort(scores)[-top_k:][::-1]
retrieval/utils.py CHANGED
@@ -1,10 +1,36 @@
1
- from typing import Dict, List
2
  import json
3
 
4
  import pandas as pd
5
 
6
 
7
- def load_documents(db_path, columns=["agent_name", "agent_description"]):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  agents_df = pd.read_csv(db_path)
9
  agent_ids = agents_df["agent_id"] # keep agent IDs (mapping back after retrieval)
10
  documents = agents_df[columns].astype(str).agg(' '.join, axis=1).tolist()
@@ -15,7 +41,7 @@ def tokenise(doc: str) -> List[str]:
15
  return doc.lower().split()
16
 
17
 
18
- def load_queries(queries_path) -> Dict[str, str]:
19
  with open(queries_path) as json_file:
20
  data = json.load(json_file)
21
  return data
 
1
+ from typing import Dict, List, Tuple
2
  import json
3
 
4
  import pandas as pd
5
 
6
 
7
+ def agentbase_indexing(db_path: str) -> Tuple[pd.DataFrame, List[str]]:
8
+ """
9
+ Another indexing configuration for AgentBase dense models.
10
+ 1. concatenate and embed all columns, except agent_id (redundant) and misc (not to go over max_seq_length)
11
+ 2. handle missing/null values
12
+ 3. prioritise important fields (see field semantics)
13
+
14
+ :returns: ids and prepared documents
15
+ """
16
+ agents_df = pd.read_csv(db_path)
17
+ agent_ids = agents_df["agent_id"]
18
+ agents_df.drop(columns=["agent_id", "misc"], inplace=True)
19
+ columns = agents_df.columns
20
+
21
+ high_priority_cols = ["agent_name", "agent_description", "agent_category"]
22
+ columns = high_priority_cols + [col for col in agents_df.columns if col not in high_priority_cols]
23
+ documents = agents_df.apply(
24
+ lambda row: ' '.join([f"{row[col]}" for col in columns if pd.notna(row[col])]), axis=1
25
+ ).tolist()
26
+ return agent_ids, documents
27
+
28
+
29
+ def load_documents(db_path: str, columns=["agent_name", "agent_description"]) -> Tuple[pd.DataFrame, List[str]]:
30
+ """
31
+ Loads documents (for sparse and dense models) by concatenating all column fields
32
+ :returns: ids and prepared documents
33
+ """
34
  agents_df = pd.read_csv(db_path)
35
  agent_ids = agents_df["agent_id"] # keep agent IDs (mapping back after retrieval)
36
  documents = agents_df[columns].astype(str).agg(' '.join, axis=1).tolist()
 
41
  return doc.lower().split()
42
 
43
 
44
+ def load_queries(queries_path: str) -> Dict[str, str]:
45
  with open(queries_path) as json_file:
46
  data = json.load(json_file)
47
  return data