From 41d21f831609844d19ea86764e06885a105f9864 Mon Sep 17 00:00:00 2001 From: Amanda Swearngin Date: Thu, 9 Oct 2025 15:30:13 -0700 Subject: [PATCH 1/7] Add interactive search script --- interactive_search/interactive_search.py | 184 +++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 interactive_search/interactive_search.py diff --git a/interactive_search/interactive_search.py b/interactive_search/interactive_search.py new file mode 100644 index 00000000..5e5ba8a5 --- /dev/null +++ b/interactive_search/interactive_search.py @@ -0,0 +1,184 @@ +"""A simple interactive search client for the agentic-economics marketplace.""" + +import argparse +import asyncio +import traceback +from datetime import datetime +from pathlib import Path + +import requests +from magentic_marketplace.experiments.utils import load_businesses_from_yaml +from magentic_marketplace.experiments.utils.yaml_loader import load_customers_from_yaml +from magentic_marketplace.marketplace.actions import ( + Search, + SearchAlgorithm, + SearchResponse, +) +from magentic_marketplace.marketplace.agents import BusinessAgent +from magentic_marketplace.marketplace.agents.customer.agent import CustomerAgent +from magentic_marketplace.marketplace.protocol.protocol import SimpleMarketplaceProtocol +from magentic_marketplace.platform.database import ( + connect_to_postgresql_database, +) +from magentic_marketplace.platform.launcher import AgentLauncher, MarketplaceLauncher + + +async def main( + data_dir: str, postgres_host: str, postgres_port: int, postgres_password: str +) -> None: + """Run a simple interactive search client for the agentic-economics marketplace.""" + # Startup platform and business tasks + businesses_dir = Path(args.data_dir) / "businesses" + customers_dir = Path(args.data_dir) / "customers" + + print(f"Loading data from: {data_dir}") + businesses = load_businesses_from_yaml(businesses_dir) + customers = load_customers_from_yaml(customers_dir) + + print(f"Loaded {len(businesses)} businesses") + print(f"Loaded {len(customers)} customers") + + experiment_name = f"marketplace_interactive_search_{len(businesses)}_{int(datetime.now().timestamp() * 1000)}" + + def database_factory(): + return connect_to_postgresql_database( + schema=experiment_name, + host=postgres_host, + port=postgres_port, + password=postgres_password, + mode="create_new", + ) + + marketplace_launcher = MarketplaceLauncher( + # host="localhost", + # port=5555, + protocol=SimpleMarketplaceProtocol(), + database_factory=database_factory, + server_log_level="warning", + experiment_name=experiment_name, + ) + + print(f"Using protocol: {marketplace_launcher.protocol.__class__.__name__}") + + async with marketplace_launcher: + print(f"Marketplace server running at: {marketplace_launcher.server_url}") + + # Create agents from loaded profiles + business_agents = [ + BusinessAgent(business, marketplace_launcher.server_url) + for business in businesses + ] + + # only create one customer agent for interactive search + customer_agent = CustomerAgent( + customers[0], + marketplace_launcher.server_url, + search_algorithm=SearchAlgorithm.LEXICAL, + ) + + # Create agent launcher and run agents with dependency management + # async with AgentLauncher(marketplace_launcher.server_url) as agent_launcher: + try: + # Startup business agents tasks only + primary_tasks = [ + asyncio.create_task(agent.run()) for agent in business_agents + ] + print(f"Started {len(primary_tasks)} tasks for business agents") + + # Startup customer agent task + customer_task = asyncio.create_task(customer_agent.run()) + print("Started task for customer agent") + + await asyncio.sleep(1) + + while True: + query = input("Query (or 'exit' to quit): ") + if query.lower() == "exit": + break + try: + print(f"Searching for: {query}") + response = await customer_agent.execute_action( + Search( + query=query, + search_algorithm=SearchAlgorithm.LEXICAL, + limit=10, + page=1, + ) + ) + if response.is_error: + print(f"Search action failed: {response.error_message}") + continue + + print("Search action succeeded") + parsed_response = SearchResponse.model_validate(response.content) + businesses_results = parsed_response.businesses + print(f"Found {len(businesses_results)} businesses:") + + # Print results + for b in businesses_results: + print(f"- {b.business.name}") + + except requests.RequestException as e: + print(f"Request failed: {e}") + traceback.print_exc() + + # Signal dependent agents (e.g., businesses) to shutdown gracefully + print(f"Signaling {len(business_agents)} dependent agents to shutdown...") + for agent in business_agents: + agent.shutdown() + + customer_agent.shutdown() + + # Give agents a brief moment to process shutdown signal + await asyncio.sleep(0.1) + + # Wait for dependent agents to complete graceful shutdown + # (includes logger cleanup in agent on_will_stop hooks) + await asyncio.gather(*primary_tasks) + await asyncio.gather(customer_task) + print("All dependent agents shut down gracefully") + + # Brief final pause to ensure all cleanup is complete + await asyncio.sleep(0.2) + + except KeyboardInterrupt: + logger.warning("Simulation interrupted by user") + + +if __name__ == "__main__": + # Add argument parsing for data dir using argparse + parser = argparse.ArgumentParser( + description="Interactive search client for the multi-agent-marketplace" + ) + parser.add_argument( + "--data-dir", help="Path to the dataset directory", required=True + ) + parser.add_argument( + "--postgres-host", + default="localhost", + help="PostgreSQL host (default: localhost)", + ) + + parser.add_argument( + "--postgres-port", + type=int, + default=5432, + help="PostgreSQL port (default: 5432)", + ) + + parser.add_argument( + "--postgres-password", + default="postgres", + help="PostgreSQL password (default: postgres)", + ) + + args = parser.parse_args() + + asyncio.run( + main( + args.data_dir, + args.postgres_host, + args.postgres_port, + args.postgres_password, + ) + ) From 614e816dabc19e8bf52a832b137153e40fec094f Mon Sep 17 00:00:00 2001 From: Amanda Swearngin Date: Thu, 9 Oct 2025 16:54:00 -0700 Subject: [PATCH 2/7] Add MRR script and calculations --- interactive_search/interactive_search.py | 142 ++------------- interactive_search/menu_mrr.py | 113 ++++++++++++ interactive_search/search_launcher.py | 210 +++++++++++++++++++++++ 3 files changed, 334 insertions(+), 131 deletions(-) create mode 100644 interactive_search/menu_mrr.py create mode 100644 interactive_search/search_launcher.py diff --git a/interactive_search/interactive_search.py b/interactive_search/interactive_search.py index 5e5ba8a5..fc227d4c 100644 --- a/interactive_search/interactive_search.py +++ b/interactive_search/interactive_search.py @@ -2,147 +2,27 @@ import argparse import asyncio -import traceback -from datetime import datetime -from pathlib import Path -import requests -from magentic_marketplace.experiments.utils import load_businesses_from_yaml -from magentic_marketplace.experiments.utils.yaml_loader import load_customers_from_yaml -from magentic_marketplace.marketplace.actions import ( - Search, - SearchAlgorithm, - SearchResponse, -) -from magentic_marketplace.marketplace.agents import BusinessAgent -from magentic_marketplace.marketplace.agents.customer.agent import CustomerAgent -from magentic_marketplace.marketplace.protocol.protocol import SimpleMarketplaceProtocol -from magentic_marketplace.platform.database import ( - connect_to_postgresql_database, -) -from magentic_marketplace.platform.launcher import AgentLauncher, MarketplaceLauncher +from search_launcher import SearchMarketLauncher async def main( data_dir: str, postgres_host: str, postgres_port: int, postgres_password: str ) -> None: """Run a simple interactive search client for the agentic-economics marketplace.""" - # Startup platform and business tasks - businesses_dir = Path(args.data_dir) / "businesses" - customers_dir = Path(args.data_dir) / "customers" - - print(f"Loading data from: {data_dir}") - businesses = load_businesses_from_yaml(businesses_dir) - customers = load_customers_from_yaml(customers_dir) - - print(f"Loaded {len(businesses)} businesses") - print(f"Loaded {len(customers)} customers") - - experiment_name = f"marketplace_interactive_search_{len(businesses)}_{int(datetime.now().timestamp() * 1000)}" - - def database_factory(): - return connect_to_postgresql_database( - schema=experiment_name, - host=postgres_host, - port=postgres_port, - password=postgres_password, - mode="create_new", - ) - - marketplace_launcher = MarketplaceLauncher( - # host="localhost", - # port=5555, - protocol=SimpleMarketplaceProtocol(), - database_factory=database_factory, - server_log_level="warning", - experiment_name=experiment_name, + search_launcher = SearchMarketLauncher( + data_dir=data_dir, + postgres_host=postgres_host, + postgres_port=postgres_port, + postgres_password=postgres_password, ) - print(f"Using protocol: {marketplace_launcher.protocol.__class__.__name__}") - - async with marketplace_launcher: - print(f"Marketplace server running at: {marketplace_launcher.server_url}") - - # Create agents from loaded profiles - business_agents = [ - BusinessAgent(business, marketplace_launcher.server_url) - for business in businesses - ] - - # only create one customer agent for interactive search - customer_agent = CustomerAgent( - customers[0], - marketplace_launcher.server_url, - search_algorithm=SearchAlgorithm.LEXICAL, - ) - - # Create agent launcher and run agents with dependency management - # async with AgentLauncher(marketplace_launcher.server_url) as agent_launcher: - try: - # Startup business agents tasks only - primary_tasks = [ - asyncio.create_task(agent.run()) for agent in business_agents - ] - print(f"Started {len(primary_tasks)} tasks for business agents") - - # Startup customer agent task - customer_task = asyncio.create_task(customer_agent.run()) - print("Started task for customer agent") - - await asyncio.sleep(1) - - while True: - query = input("Query (or 'exit' to quit): ") - if query.lower() == "exit": - break - try: - print(f"Searching for: {query}") - response = await customer_agent.execute_action( - Search( - query=query, - search_algorithm=SearchAlgorithm.LEXICAL, - limit=10, - page=1, - ) - ) - if response.is_error: - print(f"Search action failed: {response.error_message}") - continue - - print("Search action succeeded") - parsed_response = SearchResponse.model_validate(response.content) - businesses_results = parsed_response.businesses - print(f"Found {len(businesses_results)} businesses:") - - # Print results - for b in businesses_results: - print(f"- {b.business.name}") - - except requests.RequestException as e: - print(f"Request failed: {e}") - traceback.print_exc() - - # Signal dependent agents (e.g., businesses) to shutdown gracefully - print(f"Signaling {len(business_agents)} dependent agents to shutdown...") - for agent in business_agents: - agent.shutdown() - - customer_agent.shutdown() - - # Give agents a brief moment to process shutdown signal - await asyncio.sleep(0.1) - - # Wait for dependent agents to complete graceful shutdown - # (includes logger cleanup in agent on_will_stop hooks) - await asyncio.gather(*primary_tasks) - await asyncio.gather(customer_task) - print("All dependent agents shut down gracefully") - - # Brief final pause to ensure all cleanup is complete - await asyncio.sleep(0.2) + async with search_launcher.start() as _: + # Start interactive search loop + await search_launcher.interactive_search() - except KeyboardInterrupt: - logger.warning("Simulation interrupted by user") + # Shutdown + await search_launcher.stop() if __name__ == "__main__": diff --git a/interactive_search/menu_mrr.py b/interactive_search/menu_mrr.py new file mode 100644 index 00000000..8eba7bda --- /dev/null +++ b/interactive_search/menu_mrr.py @@ -0,0 +1,113 @@ +"""Run search queries based on menu items and evaluate using Mean Reciprocal Rank (MRR).""" + +import argparse +import asyncio +import random +from pathlib import Path + +from magentic_marketplace.experiments.utils.yaml_loader import load_businesses_from_yaml +from magentic_marketplace.marketplace.shared.models import BusinessAgentProfile +from search_launcher import SearchMarketLauncher + + +def has_items(items: list[str], business: BusinessAgentProfile) -> bool: + """Check if the business has all the specified menu items.""" + menu_items = business.business.menu_features.keys() + for item in items: + if item not in menu_items: + return False + return True + + +async def main( + data_dir: str, + postgres_host: str, + postgres_port: int, + postgres_password: str, + order_size: int = 1, +): + """Evaluate search functionality using Mean Reciprocal Rank (MRR) based on menu items.""" + # Start the search market server before running this script + search_launcher = SearchMarketLauncher( + data_dir=data_dir, + postgres_host=postgres_host, + postgres_port=postgres_port, + postgres_password=postgres_password, + ) + async with search_launcher.start() as _: + reciprocal_ranks: list[float] = [] + businesses_dir = Path(args.data_dir) / "businesses" + + businesses = load_businesses_from_yaml(businesses_dir) + + for business in businesses: + menu_items = list(business.menu_features.keys()) + sampled_items = random.sample(menu_items, min(order_size, len(menu_items))) + + query = " ".join(sampled_items) + print(f"Searching for: {query}") + results = await search_launcher.search(query=query) + + found = False + rank = 0 + for rank in range(len(results)): + result = results[rank] + if has_items(sampled_items, result): + print( + f"Found matching business at rank {rank + 1}: {result.business.name}" + ) + found = True + break + if found: + reciprocal_ranks.append(1 / (rank + 1)) + else: + print(f"No matching business found in top {len(results)} results.") + reciprocal_ranks.append(1 / (len(results) + 1)) + print() + + print("--- Evaluation Complete ---") + print( + f"Mean Reciprocal Rank (MRR): {sum(reciprocal_ranks) / len(reciprocal_ranks) if reciprocal_ranks else 0:.4f}" + ) + + # Shutdown + print("Shutting down...") + await search_launcher.stop() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Compute Mean Reciprocal Rank (MRR) for search queries based on menu items" + ) + parser.add_argument( + "--data-dir", help="Path to the dataset directory", required=True + ) + parser.add_argument( + "--postgres-host", + default="localhost", + help="PostgreSQL host (default: localhost)", + ) + + parser.add_argument( + "--postgres-port", + type=int, + default=5432, + help="PostgreSQL port (default: 5432)", + ) + + parser.add_argument( + "--postgres-password", + default="postgres", + help="PostgreSQL password (default: postgres)", + ) + + args = parser.parse_args() + + asyncio.run( + main( + args.data_dir, + args.postgres_host, + args.postgres_port, + args.postgres_password, + ) + ) diff --git a/interactive_search/search_launcher.py b/interactive_search/search_launcher.py new file mode 100644 index 00000000..2e02a796 --- /dev/null +++ b/interactive_search/search_launcher.py @@ -0,0 +1,210 @@ +"""Module to launch a marketplace server with businesses and a single customer agent for interactive search experiments.""" + +import asyncio +import traceback +from contextlib import asynccontextmanager +from datetime import datetime +from pathlib import Path + +import requests +from magentic_marketplace.experiments.utils.yaml_loader import ( + load_businesses_from_yaml, + load_customers_from_yaml, +) +from magentic_marketplace.marketplace.actions.actions import ( + Search, + SearchAlgorithm, + SearchResponse, +) +from magentic_marketplace.marketplace.agents.business.agent import BusinessAgent +from magentic_marketplace.marketplace.agents.customer.agent import CustomerAgent +from magentic_marketplace.marketplace.protocol.protocol import SimpleMarketplaceProtocol +from magentic_marketplace.marketplace.shared.models import BusinessAgentProfile +from magentic_marketplace.platform.database.postgresql.postgresql import ( + connect_to_postgresql_database, +) +from magentic_marketplace.platform.launcher import MarketplaceLauncher + + +class SearchMarketLauncher: + """Class to manage launching the marketplace server with a set of business agents and a single customer agent.""" + + def __init__( + self, + data_dir: str, + postgres_host: str, + postgres_port: int, + postgres_password: str, + ): + """Initialize the launcher with empty lists for agents and tasks.""" + self.business_agents = [] + self.customer_agent = None + self.tasks = [] + self.marketplace_launcher = None + + self.business_profiles = [] + self.customer_profiles = [] + + self.load_data( + data_dir=data_dir, + postgres_host=postgres_host, + postgres_port=postgres_port, + postgres_password=postgres_password, + ) + + def load_data( + self, + data_dir: str, + postgres_host: str, + postgres_port: int, + postgres_password: str, + ): + """Load businesses and customers from YAML files.""" + businesses_dir = Path(data_dir) / "businesses" + customers_dir = Path(data_dir) / "customers" + + print(f"Loading data from: {data_dir}") + self.business_profiles = load_businesses_from_yaml(businesses_dir) + self.customer_profiles = load_customers_from_yaml(customers_dir) + + print(f"Loaded {len(self.business_profiles)} businesses") + print(f"Loaded {len(self.customer_profiles)} customers") + + experiment_name = f"marketplace_interactive_search_{len(self.business_profiles)}_{int(datetime.now().timestamp() * 1000)}" + + def database_factory(): + return connect_to_postgresql_database( + schema=experiment_name, + host=postgres_host, + port=postgres_port, + password=postgres_password, + mode="create_new", + ) + + self.marketplace_launcher = MarketplaceLauncher( + protocol=SimpleMarketplaceProtocol(), + database_factory=database_factory, + server_log_level="warning", + experiment_name=experiment_name, + ) + + print( + f"Using protocol: {self.marketplace_launcher.protocol.__class__.__name__}" + ) + + @asynccontextmanager + async def start(self): + """Startup platform, businesses, and customer task.""" + async with self.marketplace_launcher: + print( + f"Marketplace server running at: {self.marketplace_launcher.server_url}" + ) + + # Create agents from loaded profiles + business_agents = [ + BusinessAgent(business, self.marketplace_launcher.server_url) + for business in self.business_profiles + ] + self.business_agents.extend(business_agents) + + # only create one customer agent for interactive search + customer_agent = CustomerAgent( + self.customer_profiles[0], + self.marketplace_launcher.server_url, + search_algorithm=SearchAlgorithm.LEXICAL, + ) + self.customer_agent = customer_agent + + # Create agent launcher and run agents with dependency management + # async with AgentLauncher(marketplace_launcher.server_url) as agent_launcher: + try: + # Startup business agents tasks only + primary_tasks = [ + asyncio.create_task(agent.run()) for agent in business_agents + ] + self.tasks.extend(primary_tasks) + print(f"Started {len(primary_tasks)} tasks for business agents") + + # Startup customer agent task + customer_task = asyncio.create_task(customer_agent.run()) + self.tasks.append(customer_task) + + print("Started task for customer agent") + + await asyncio.sleep(1) + except KeyboardInterrupt: + print("Marketplace interrupted by user") + + yield self.marketplace_launcher + + async def stop(self): + """Stop all running tasks and agents.""" + for agent in self.business_agents: + agent.shutdown() + + if self.customer_agent: + self.customer_agent.shutdown() + + await asyncio.sleep(0.1) + + await asyncio.gather(*self.tasks) + + await asyncio.sleep(0.2) + + async def search(self, query) -> list[BusinessAgentProfile]: + """Issue search queries using the customer agent and return the resulting business profiles.""" + try: + response = await self.customer_agent.execute_action( + Search( + query=query, + search_algorithm=SearchAlgorithm.LEXICAL, + limit=10, + page=1, + ) + ) + if response.is_error: + print(f"Search action failed: {response.error_message}") + return + + parsed_response = SearchResponse.model_validate(response.content) + businesses_results = parsed_response.businesses + + # Print results + return businesses_results + + except requests.RequestException as e: + print(f"Request failed: {e}") + traceback.print_exc() + + async def interactive_search(self): + """Issue search queries interactively from the customer agent.""" + while True: + query = input("Query (or 'exit' to quit): ") + if query.lower() == "exit": + break + try: + print(f"Searching for: {query}") + response = await self.customer_agent.execute_action( + Search( + query=query, + search_algorithm=SearchAlgorithm.LEXICAL, + limit=10, + page=1, + ) + ) + if response.is_error: + print(f"Search action failed: {response.error_message}") + continue + + print("Search action succeeded") + parsed_response = SearchResponse.model_validate(response.content) + businesses_results = parsed_response.businesses + print(f"Found {len(businesses_results)} businesses:") + + # Print results + for b in businesses_results: + print(f"- {b.business.name}") + + except requests.RequestException as e: + print(f"Request failed: {e}") + traceback.print_exc() From 6f9664f5fe370cb80f8329d02d31bfd0ffdecda7 Mon Sep 17 00:00:00 2001 From: Amanda Swearngin Date: Thu, 9 Oct 2025 17:05:20 -0700 Subject: [PATCH 3/7] Add instructions to README --- interactive_search/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 interactive_search/README.md diff --git a/interactive_search/README.md b/interactive_search/README.md new file mode 100644 index 00000000..0a693484 --- /dev/null +++ b/interactive_search/README.md @@ -0,0 +1,26 @@ +# Script for Working with the Marketplace Search API + +This directory provides convenience scripts for interacting with the search API of the +Marketplace, and for running basic evaluations. + +### Interactive Search + +To run the interactive search script, use the following command: + +```bash +python interactive_search.py --data-dir ../data/mexican_3_9 +``` + +### Evaluation + +Evaluation is done by randomly sampling a menu item from EACH restaurant in the dataset, +and for each item, determining the rank of the FIRST restaurant that contains that item. + +From these ranks we compute [mean reciprocal rank](https://en.wikipedia.org/wiki/Mean_reciprocal_rank) (MRR) as the evaluation metric. + +Values closer to 1.0 are better, with 1.0 being perfect. + +To run the evaluation script, use the following command: +``` +python menu_mrr.py --data-dir ../data/mexican_3_9 +``` \ No newline at end of file From 3c5830b4d1d842e462ac1e4c9c54dd2c3b164207 Mon Sep 17 00:00:00 2001 From: Amanda Swearngin Date: Thu, 9 Oct 2025 17:35:18 -0700 Subject: [PATCH 4/7] Enable changing the search algorithm --- interactive_search/interactive_search.py | 13 ++++++++++++- interactive_search/menu_mrr.py | 16 ++++++++++++++++ interactive_search/search_launcher.py | 17 +++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/interactive_search/interactive_search.py b/interactive_search/interactive_search.py index fc227d4c..242ad56d 100644 --- a/interactive_search/interactive_search.py +++ b/interactive_search/interactive_search.py @@ -7,7 +7,11 @@ async def main( - data_dir: str, postgres_host: str, postgres_port: int, postgres_password: str + data_dir: str, + postgres_host: str, + postgres_port: int, + postgres_password: str, + search_algorithm: str = "lexical", ) -> None: """Run a simple interactive search client for the agentic-economics marketplace.""" search_launcher = SearchMarketLauncher( @@ -15,6 +19,7 @@ async def main( postgres_host=postgres_host, postgres_port=postgres_port, postgres_password=postgres_password, + search_algorithm=search_algorithm, ) async with search_launcher.start() as _: @@ -33,6 +38,11 @@ async def main( parser.add_argument( "--data-dir", help="Path to the dataset directory", required=True ) + parser.add_argument( + "--search-algorithm", + default="lexical", + help="Search algorithm to use (default: lexical)", + ) parser.add_argument( "--postgres-host", default="localhost", @@ -60,5 +70,6 @@ async def main( args.postgres_host, args.postgres_port, args.postgres_password, + args.search_algorithm, ) ) diff --git a/interactive_search/menu_mrr.py b/interactive_search/menu_mrr.py index 8eba7bda..7339fec5 100644 --- a/interactive_search/menu_mrr.py +++ b/interactive_search/menu_mrr.py @@ -25,6 +25,7 @@ async def main( postgres_port: int, postgres_password: str, order_size: int = 1, + search_algorithm: str = "lexical", ): """Evaluate search functionality using Mean Reciprocal Rank (MRR) based on menu items.""" # Start the search market server before running this script @@ -33,6 +34,7 @@ async def main( postgres_host=postgres_host, postgres_port=postgres_port, postgres_password=postgres_password, + search_algorithm=search_algorithm, ) async with search_launcher.start() as _: reciprocal_ranks: list[float] = [] @@ -82,6 +84,18 @@ async def main( parser.add_argument( "--data-dir", help="Path to the dataset directory", required=True ) + parser.add_argument( + "--order-size", + type=int, + default=1, + help="Number of menu items to include in each search query (default: 1)", + ) + parser.add_argument( + "--search-algorithm", + type=str, + default="lexical", + help="Search algorithm to use (default: lexical)", + ) parser.add_argument( "--postgres-host", default="localhost", @@ -109,5 +123,7 @@ async def main( args.postgres_host, args.postgres_port, args.postgres_password, + args.order_size, + args.search_algorithm, ) ) diff --git a/interactive_search/search_launcher.py b/interactive_search/search_launcher.py index 2e02a796..2a9bba99 100644 --- a/interactive_search/search_launcher.py +++ b/interactive_search/search_launcher.py @@ -35,6 +35,7 @@ def __init__( postgres_host: str, postgres_port: int, postgres_password: str, + search_algorithm: str = "lexical", ): """Initialize the launcher with empty lists for agents and tasks.""" self.business_agents = [] @@ -42,6 +43,22 @@ def __init__( self.tasks = [] self.marketplace_launcher = None + # Get the SearchAlgorithm enum value from the string value provided + if search_algorithm.lower() == "lexical": + self.search_algorithm = SearchAlgorithm.LEXICAL + elif search_algorithm.lower() == "optimal": + self.search_algorithm = SearchAlgorithm.OPTIMAL + elif search_algorithm.lower() == "filtered": + self.search_algorithm = SearchAlgorithm.FILTERED + elif search_algorithm.lower() == "simple": + self.search_algorithm = SearchAlgorithm.SIMPLE + elif search_algorithm.lower() == "rnr": + self.search_algorithm = SearchAlgorithm.RNR + else: + raise ValueError(f"Invalid search algorithm: {search_algorithm}") + + self.search_algorithm = search_algorithm + self.business_profiles = [] self.customer_profiles = [] From 5d9137fbd115e146aabe296abc4a80bd60bf63fc Mon Sep 17 00:00:00 2001 From: Amanda Swearngin Date: Thu, 9 Oct 2025 17:41:21 -0700 Subject: [PATCH 5/7] Customize the search algorithm --- interactive_search/search_launcher.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interactive_search/search_launcher.py b/interactive_search/search_launcher.py index 2a9bba99..0aeba764 100644 --- a/interactive_search/search_launcher.py +++ b/interactive_search/search_launcher.py @@ -128,7 +128,7 @@ async def start(self): customer_agent = CustomerAgent( self.customer_profiles[0], self.marketplace_launcher.server_url, - search_algorithm=SearchAlgorithm.LEXICAL, + search_algorithm=self.search_algorithm, ) self.customer_agent = customer_agent @@ -174,7 +174,7 @@ async def search(self, query) -> list[BusinessAgentProfile]: response = await self.customer_agent.execute_action( Search( query=query, - search_algorithm=SearchAlgorithm.LEXICAL, + search_algorithm=self.search_algorithm, limit=10, page=1, ) @@ -204,7 +204,7 @@ async def interactive_search(self): response = await self.customer_agent.execute_action( Search( query=query, - search_algorithm=SearchAlgorithm.LEXICAL, + search_algorithm=self.search_algorithm, limit=10, page=1, ) From 92db8cca20261061c2341c3464e1de6827f08b2f Mon Sep 17 00:00:00 2001 From: Amanda Swearngin Date: Fri, 10 Oct 2025 08:54:34 -0700 Subject: [PATCH 6/7] Add argument for showing all searchable text in results --- interactive_search/interactive_search.py | 12 +++++++++++- interactive_search/search_launcher.py | 12 ++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/interactive_search/interactive_search.py b/interactive_search/interactive_search.py index 242ad56d..538b09b0 100644 --- a/interactive_search/interactive_search.py +++ b/interactive_search/interactive_search.py @@ -12,6 +12,7 @@ async def main( postgres_port: int, postgres_password: str, search_algorithm: str = "lexical", + show_all_searchable_text: bool = False, ) -> None: """Run a simple interactive search client for the agentic-economics marketplace.""" search_launcher = SearchMarketLauncher( @@ -24,7 +25,9 @@ async def main( async with search_launcher.start() as _: # Start interactive search loop - await search_launcher.interactive_search() + await search_launcher.interactive_search( + show_all_searchable_text=show_all_searchable_text + ) # Shutdown await search_launcher.stop() @@ -62,6 +65,12 @@ async def main( help="PostgreSQL password (default: postgres)", ) + parser.add_argument( + "--show-all-searchable-text", + action="store_true", + help="Show all searchable text for searched businesses for debugging (default: False)", + ) + args = parser.parse_args() asyncio.run( @@ -71,5 +80,6 @@ async def main( args.postgres_port, args.postgres_password, args.search_algorithm, + args.show_all_searchable_text, ) ) diff --git a/interactive_search/search_launcher.py b/interactive_search/search_launcher.py index 0aeba764..db8f7e8a 100644 --- a/interactive_search/search_launcher.py +++ b/interactive_search/search_launcher.py @@ -27,7 +27,7 @@ class SearchMarketLauncher: - """Class to manage launching the marketplace server with a set of business agents and a single customer agent.""" + """Class to manage launching the marketplace server for interactive search experiments with a set of business agents and a single customer agent.""" def __init__( self, @@ -133,7 +133,6 @@ async def start(self): self.customer_agent = customer_agent # Create agent launcher and run agents with dependency management - # async with AgentLauncher(marketplace_launcher.server_url) as agent_launcher: try: # Startup business agents tasks only primary_tasks = [ @@ -193,7 +192,7 @@ async def search(self, query) -> list[BusinessAgentProfile]: print(f"Request failed: {e}") traceback.print_exc() - async def interactive_search(self): + async def interactive_search(self, show_all_searchable_text: bool = False): """Issue search queries interactively from the customer agent.""" while True: query = input("Query (or 'exit' to quit): ") @@ -220,7 +219,12 @@ async def interactive_search(self): # Print results for b in businesses_results: - print(f"- {b.business.name}") + if show_all_searchable_text: + print( + f"- {b.business.name}: {b.business.get_searchable_text()}" + ) + else: + print(f"- {b.business.name}") except requests.RequestException as e: print(f"Request failed: {e}") From e83184157df3fb106894e041e1de9230d1f0a80a Mon Sep 17 00:00:00 2001 From: Amanda Swearngin Date: Fri, 10 Oct 2025 09:01:15 -0700 Subject: [PATCH 7/7] Use issubset --- interactive_search/menu_mrr.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/interactive_search/menu_mrr.py b/interactive_search/menu_mrr.py index 7339fec5..ab1ba762 100644 --- a/interactive_search/menu_mrr.py +++ b/interactive_search/menu_mrr.py @@ -12,11 +12,8 @@ def has_items(items: list[str], business: BusinessAgentProfile) -> bool: """Check if the business has all the specified menu items.""" - menu_items = business.business.menu_features.keys() - for item in items: - if item not in menu_items: - return False - return True + menu_items = set(business.business.menu_features.keys()) + return set(items).issubset(menu_items) async def main(