esemsc-am4224 commited on
Commit
98aa770
·
1 Parent(s): aaa9a19

feat: added 2 retrieval models

Browse files
.gitignore ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.DS_Store
2
+ # Byte-compiled / optimized / DLL files
3
+ __pycache__/
4
+ *.py[codz]
5
+ *$py.class
6
+
7
+ # C extensions
8
+ *.so
9
+
10
+ # Distribution / packaging
11
+ .Python
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py.cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ #Pipfile.lock
97
+
98
+ # UV
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ #uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ #poetry.lock
110
+ #poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ #pdm.lock
117
+ #pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ #pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi
127
+
128
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
129
+ __pypackages__/
130
+
131
+ # Celery stuff
132
+ celerybeat-schedule
133
+ celerybeat.pid
134
+
135
+ # SageMath parsed files
136
+ *.sage.py
137
+
138
+ # Environments
139
+ .env
140
+ .envrc
141
+ .venv
142
+ env/
143
+ venv/
144
+ ENV/
145
+ env.bak/
146
+ venv.bak/
147
+
148
+ # Spyder project settings
149
+ .spyderproject
150
+ .spyproject
151
+
152
+ # Rope project settings
153
+ .ropeproject
154
+
155
+ # mkdocs documentation
156
+ /site
157
+
158
+ # mypy
159
+ .mypy_cache/
160
+ .dmypy.json
161
+ dmypy.json
162
+
163
+ # Pyre type checker
164
+ .pyre/
165
+
166
+ # pytype static type analyzer
167
+ .pytype/
168
+
169
+ # Cython debug symbols
170
+ cython_debug/
171
+
172
+ # PyCharm
173
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
174
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
175
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
176
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
177
+ #.idea/
178
+
179
+ # Abstra
180
+ # Abstra is an AI-powered process automation framework.
181
+ # Ignore directories containing user credentials, local state, and settings.
182
+ # Learn more at https://abstra.io/docs
183
+ .abstra/
184
+
185
+ # Visual Studio Code
186
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
187
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
188
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
189
+ # you could uncomment the following to ignore the entire vscode folder
190
+ # .vscode/
191
+
192
+ # Ruff stuff:
193
+ .ruff_cache/
194
+
195
+ # PyPI configuration file
196
+ .pypirc
197
+
198
+ # Cursor
199
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
200
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
201
+ # refer to https://docs.cursor.com/context/ignore-files
202
+ .cursorignore
203
+ .cursorindexingignore
204
+
205
+ # Marimo
206
+ marimo/_static/
207
+ marimo/_lsp/
208
+ __marimo__/
data/agentbase.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f8ac2555a8cacd01b1e319abe638b5da638451b67cd247350acd3fe46e71599e
3
+ size 13923827
platform/agentbase.db → data/agentbase_mini.csv RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:55ff49e995742f88b3efa6d0736a58bcf47b987dbfa91a228f5ad1c64a8a03db
3
- size 6701056
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cc54651a1cb6d275ea43b663e324efc3181bd0dfd2d9eb1df04dff81cd6c9ff9
3
+ size 439127
data/embeddings/BAAI_bge-large-en-v1.5_6380768bd184d185.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cffc46d72e08a7acf60b72a587896bc581f5129ecba306f059db31b435b4cdb3
3
+ size 1144547
data/embeddings/BAAI_bge-large-en-v1.5_88f95605539a0823.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:75f3cfb24da3dba8c17590bbe7d59958f2bcc31d6cb6547e20d791d045453393
3
+ size 37193250
data/platforms.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5acfd47ac8cabf9e6937907dcd59b2f3ba933475032b2a98b77014b0ae5d1096
3
+ size 267
data/queries.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "q:01": "Strategies to enhance the efficiency and responsiveness of our company's customer support services.",
3
+ "q:02": "Explain Newton’s first law of motion and its implications for objects in uniform motion.",
4
+ "q:03": "Design personalised fitness and nutrition programs tailored to individual goals and needs."
5
+ }
platform/interface.py → interface.py RENAMED
@@ -5,84 +5,78 @@ Author: Arastun Mammadli
5
  Date: [Current Date]
6
  """
7
 
 
8
  import streamlit as st
9
  import pandas as pd
10
  import numpy as np
11
- from pathlib import Path
12
 
13
- from database import SQLiteConnector
 
 
14
 
15
 
16
- def retrieve_data(df, search_term, search_columns, method='baseline'):
17
- """
18
- Retrieve data from the loaded DataFrame based on the specified method.
19
- """
20
-
21
- if method == 'baseline': return keyword_filter(df, search_term, search_columns)
22
-
23
- def keyword_filter(df, keyword, columns):
24
  """
25
  Simple keyword-based boolean filter across specified columns.
26
  """
27
-
28
- if not keyword: return df
29
 
30
  mask = df[columns].astype(str).apply(
31
- lambda col: col.str.contains(keyword, case=False, na=False)
32
  ).any(axis=1)
33
- return df[mask]
34
-
 
35
 
36
  class AgentBaseUI:
37
  """
38
  AgentBase Streamlit-based UI Components.
39
  """
40
 
41
- def __init__(self, db_path):
42
- # loading the data
43
- self.db_connector = SQLiteConnector(db_path)
44
- self.platforms_df, self.agents_df = self.db_connector.load_all_as_df()
 
 
 
 
 
 
 
45
 
46
  def show_header(self):
47
  st.set_page_config(page_title="AgentBase", layout="wide")
48
  st.title("AgentBase Platform Demo")
49
  st.write("A Large-Scale Agent Collection for Automated Agent Recommendation.")
50
  st.subheader("🔍 Retrieval")
51
- self.search_term = st.text_input("Test our retrieval models", placeholder="Type to search...")
52
-
53
- def show_platforms(self):
54
- st.header("Platforms")
55
- platforms_filtered = retrieve_data(
56
- self.platforms_df,
57
- self.search_term,
58
- ['platform_id', 'platform_name', 'platform_url']
59
- )
60
-
61
- if len(platforms_filtered) > 0:
62
- st.write(f"Showing {len(platforms_filtered)} of {len(self.platforms_df)} platforms")
63
- # cropped display
64
- platform_config = {
65
- "platform_url": st.column_config.LinkColumn("platform_url", display_text="Visit →"),
66
- }
67
- st.dataframe(
68
- platforms_filtered,
69
- column_config=platform_config,
70
- use_container_width=True,
71
- hide_index=True
72
  )
73
- else:
74
- st.info("No platforms match your search.")
75
 
76
  def show_agents(self):
77
  st.header("Agents")
78
- agents_filtered = retrieve_data(
79
- self.agents_df,
80
- self.search_term,
81
- ['agent_id', 'agent_name', 'agent_description', 'agent_category']
82
- )
83
-
84
- if len(agents_filtered) > 0:
85
- st.write(f"Showing {len(agents_filtered)} of {len(self.agents_df)} agents")
86
  # cropped display
87
  agent_config = {
88
  "agent_url": st.column_config.LinkColumn("agent_url", display_text="Visit →"),
@@ -93,15 +87,16 @@ class AgentBaseUI:
93
  # option to show details
94
  if st.toggle("Show detailed view"):
95
  st.dataframe(
96
- agents_filtered,
97
  column_config=agent_config,
98
  use_container_width=True,
99
  hide_index=True
100
  )
101
  else:
102
- key_columns = ['agent_name', 'platform_id', 'agent_category', 'agent_accessibility', 'agent_url']
 
103
  st.dataframe(
104
- agents_filtered[key_columns],
105
  column_config=agent_config,
106
  use_container_width=True,
107
  hide_index=True
@@ -109,11 +104,50 @@ class AgentBaseUI:
109
  else:
110
  st.info("No agents match your search.")
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  if __name__ == "__main__":
114
- BASE_DIR = Path(__file__).parent
115
- DB_PATH = BASE_DIR / "agentbase.db"
116
- agentbaseui = AgentBaseUI(db_path=DB_PATH)
117
  agentbaseui.show_header()
118
  agentbaseui.show_agents()
119
  agentbaseui.show_platforms()
 
5
  Date: [Current Date]
6
  """
7
 
8
+ from typing import List, Tuple
9
  import streamlit as st
10
  import pandas as pd
11
  import numpy as np
 
12
 
13
+ from retrieval.models.bm25 import BM25Retriever
14
+ 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
  """
22
+ if not query: return df
 
23
 
24
  mask = df[columns].astype(str).apply(
25
+ lambda col: col.str.contains(query, case=False, na=False)
26
  ).any(axis=1)
27
+ filtered_df = df[mask].head(top_k)
28
+ filtered_df["scores"] = 1
29
+ return filtered_df
30
 
31
  class AgentBaseUI:
32
  """
33
  AgentBase Streamlit-based UI Components.
34
  """
35
 
36
+ def __init__(self, agentbase_path, platforms_path):
37
+ self.agents_df = pd.read_csv(agentbase_path)
38
+ self.agents_df['scores'] = 0.0
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")
50
  st.title("AgentBase Platform Demo")
51
  st.write("A Large-Scale Agent Collection for Automated Agent Recommendation.")
52
  st.subheader("🔍 Retrieval")
53
+ if "query" not in st.session_state:
54
+ st.session_state.query = ""
55
+
56
+ query_suggestions = list(load_queries("data/queries.json").values())
57
+ suggestion_cols = st.columns(len(query_suggestions))
58
+ for i, suggestion in enumerate(query_suggestions):
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")
74
+ filtered_df = self.retrieve_agents(st.session_state.query) if st.session_state.query else self.agents_df
75
+ top_k = st.slider("Top K", 3, 100, 5) if st.session_state.query else len(filtered_df)
76
+ top_k = min(top_k, len(filtered_df))
77
+
78
+ if len(filtered_df) > 0:
79
+ st.write(f"Showing {top_k} of {len(self.agents_df)} agents")
 
 
80
  # cropped display
81
  agent_config = {
82
  "agent_url": st.column_config.LinkColumn("agent_url", display_text="Visit →"),
 
87
  # option to show details
88
  if st.toggle("Show detailed view"):
89
  st.dataframe(
90
+ filtered_df.head(top_k),
91
  column_config=agent_config,
92
  use_container_width=True,
93
  hide_index=True
94
  )
95
  else:
96
+ key_columns = ['agent_name', 'platform_name', 'agent_description', 'agent_url', 'scores']
97
+ if (filtered_df['scores'] == 0).all(): key_columns.remove("scores")
98
  st.dataframe(
99
+ filtered_df[key_columns].head(top_k),
100
  column_config=agent_config,
101
  use_container_width=True,
102
  hide_index=True
 
104
  else:
105
  st.info("No agents match your search.")
106
 
107
+ def retrieve_agents(self, query, top_k=100) -> pd.DataFrame:
108
+ """
109
+ Returns a filtered dataframe with updated scores.
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
+
121
+ self.agents_df["scores"] = 0 # reset
122
+ agent_ids, _ = zip(*res)
123
+ filtered_df = self.agents_df.loc[self.agents_df.agent_id.isin(agent_ids)]
124
+ for index, row in filtered_df.iterrows():
125
+ score = dict(res).get(row['agent_id'], 0)
126
+ filtered_df.at[index, 'scores'] = score
127
+ return filtered_df.sort_values(by="scores", ascending=False)
128
+
129
+ def show_platforms(self):
130
+ st.header("Platforms")
131
+ platforms_filtered = self.platforms_df
132
+
133
+ if len(platforms_filtered) > 0:
134
+ st.write(f"Showing {len(platforms_filtered)} of {len(self.platforms_df)} platforms")
135
+ # cropped display
136
+ platform_config = {
137
+ "platform_url": st.column_config.LinkColumn("platform_url", display_text="Visit →"),
138
+ }
139
+ st.dataframe(
140
+ platforms_filtered,
141
+ column_config=platform_config,
142
+ use_container_width=True,
143
+ hide_index=True
144
+ )
145
+ else:
146
+ st.info("No platforms match your search.")
147
+
148
 
149
  if __name__ == "__main__":
150
+ agentbaseui = AgentBaseUI(agentbase_path="data/agentbase.csv", platforms_path="data/platforms.csv")
 
 
151
  agentbaseui.show_header()
152
  agentbaseui.show_agents()
153
  agentbaseui.show_platforms()
platform/__pycache__/__init__.cpython-312.pyc DELETED
Binary file (171 Bytes)
 
platform/__pycache__/database.cpython-312.pyc DELETED
Binary file (5.61 kB)
 
platform/database.py DELETED
@@ -1,123 +0,0 @@
1
- """
2
- AgentBase SQLite3 Database Connector.
3
-
4
- This module implements general-purpose functions to access, manipulate and query the AgentBase
5
- database platform.
6
- 1. Schema Initialisation
7
- 2. Database Connection and Query Execution
8
- 3. Data Insertion (e.g., from temporary CSV files, TODO: updates through web scraping)
9
-
10
- Author: Arastun Mammadli
11
- Date: [Current Date]
12
- """
13
-
14
- import json
15
- import pandas as pd
16
- import numpy as np
17
- import sqlite3
18
-
19
-
20
- class SQLiteConnector:
21
- """
22
- A class to handle SQLite database connections and operations.
23
- """
24
-
25
- def __init__(self, database_file):
26
- self.conn = sqlite3.connect(database_file)
27
- self.cursor = self.conn.cursor()
28
- self.init_schema()
29
-
30
- def init_schema(self):
31
- """
32
- Initialise the datbase schema (run once).
33
- (1) Platform Entity, (2) Agent Entity, (3) Embeddings Table (optional)
34
- """
35
- self.conn.execute("""
36
- CREATE TABLE IF NOT EXISTS platforms (
37
- platform_id TEXT PRIMARY KEY,
38
- platform_name TEXT,
39
- platform_url TEXT
40
- )
41
- """)
42
- self.conn.execute("""
43
- CREATE TABLE IF NOT EXISTS agents (
44
- agent_id TEXT PRIMARY KEY,
45
- platform_id INTEGER,
46
- agent_name TEXT,
47
- platform_name TEXT,
48
- agent_description TEXT,
49
- agent_category TEXT,
50
- agent_usage TEXT, -- JSON string
51
- usage_example TEXT, -- JSON string
52
- agent_url TEXT,
53
- agent_accessibility TEXT,
54
- update_time TEXT,
55
- misc TEXT,
56
- FOREIGN KEY (platform_id) REFERENCES platforms(platform_id)
57
- )
58
- """)
59
- self.conn.execute("""
60
- CREATE TABLE IF NOT EXISTS embeddings (
61
- agent_id TEXT,
62
- embedding_model TEXT, -- e.g., 'all-MiniLM-L6-v2'
63
- field TEXT, -- 'description', 'combined', etc.
64
- embedding BLOB, -- numpy array as bytes
65
- PRIMARY KEY (agent_id, embedding_model, field),
66
- FOREIGN KEY (agent_id) REFERENCES agents(agent_id)
67
- )
68
- """)
69
- self.conn.commit()
70
-
71
- def insert_platform(self, platform_csv):
72
- """
73
- Insert a mock platform from a CSV file
74
- """
75
-
76
- df = pd.read_csv(platform_csv)
77
- df.to_sql('platforms', self.conn, if_exists='replace', index=False)
78
- self.conn.commit()
79
-
80
- def insert_agent(self, agent_csv):
81
- """
82
- Insert mock agents from a CSV file
83
- """
84
-
85
- df = pd.read_csv(agent_csv)
86
- df.to_sql('agents', self.conn, if_exists='replace', index=False)
87
- self.conn.commit()
88
-
89
- def execute_query(self, query, params=None):
90
- if params:
91
- self.cursor.execute(query, params)
92
- else:
93
- self.cursor.execute(query)
94
- self.conn.commit()
95
- return self.cursor.fetchall()
96
-
97
- def load_all_as_df(self):
98
- """
99
- Loads all platforms and agents from database as a pandas dataframe.
100
- """
101
-
102
- platforms_df = pd.read_sql_query("SELECT * FROM platforms", self.conn)
103
- agents_df = pd.read_sql_query("SELECT * FROM agents", self.conn)
104
- return platforms_df, agents_df
105
-
106
- def close(self):
107
- self.conn.close()
108
-
109
-
110
- if __name__ == "__main__":
111
- db_file = "agentbase.db"
112
- platforms_csv = f"../data_collection/extracted_data/platforms.csv"
113
- agents_csv = f"../data_collection/extracted_data/cleaned/agentbase_cleaned.csv"
114
-
115
- # connect & insert
116
- db_connector = SQLiteConnector(db_file)
117
- db_connector.insert_platform(platforms_csv)
118
- db_connector.insert_agent(agents_csv)
119
-
120
- # test
121
- # platforms, agents = db_connector.load_all_as_df()
122
- # print(platforms)
123
- db_connector.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
retrieval/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
retrieval/base.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Tuple, Dict
3
+
4
+
5
+ class BaseRetriever(ABC):
6
+ """
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."""
19
+ pass
20
+
21
+ @abstractmethod
22
+ def retrieve(self, query: str, top_k: int = 10) -> List[Tuple[str, float]]:
23
+ """
24
+ Retrieve top-k agents for a single query.
25
+
26
+ Returns:
27
+ List of (agent_id, score) tuples
28
+ """
29
+ pass
30
+
31
+ def batch_retrieve(self, queries: Dict[str, str], top_k: int = 10) -> Dict[str, List[Tuple[str, float]]]:
32
+ """Retrieve for multiple queries (for evaluation)."""
33
+ results = {}
34
+ for qid, query in queries.items():
35
+ results[qid] = self.retrieve(query, top_k)
36
+ return results
{platform → retrieval/models}/__init__.py RENAMED
File without changes
retrieval/models/bm25.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()
18
+
19
+ def build_index(self):
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
+
27
+ def retrieve(self, query: str, top_k: int = 10):
28
+ """
29
+ Retrieve top-k agents using BM25.
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]
retrieval/models/sentence_bert.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
21
+ self.embeddings_path = self._default_embeddings_path()
22
+ self.load_index() if self._embeddings_exist() else self.build_index()
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]
34
+ return f"data/embeddings/{model_safe}_{hash_str}.npz"
35
+
36
+ def _embeddings_exist(self) -> bool:
37
+ return Path(self.embeddings_path).exists()
38
+
39
+ def _store_index(self):
40
+ path = Path(self.embeddings_path)
41
+ path.parent.mkdir(parents=True, exist_ok=True)
42
+ np.savez_compressed(
43
+ path,
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):
51
+ """
52
+ Use precomputed embeddings.
53
+ """
54
+ data = np.load(self.embeddings_path, allow_pickle=True)
55
+ self.corpus_embeddings = data['embeddings']
56
+ self.agent_ids = data['agent_ids'].tolist()
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,
74
+ convert_to_numpy=True,
75
+ )
76
+ self._store_index() # avoid re-building
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]
85
+ return [(self.agent_ids[idx], float(scores[idx])) for idx in top_indices]
retrieval/utils.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()
11
+ return agent_ids, documents
12
+
13
+
14
+ 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