From 0be561d1322fc03ba29c07b48f18dbc2e1e61f46 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:32:43 +0800 Subject: [PATCH 01/24] added tripadvisor APIs as tool functions --- src/agentscope/service/__init__.py | 8 + src/agentscope/service/web/tripadvisor.py | 197 ++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 src/agentscope/service/web/tripadvisor.py diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 2a0ba3e53..4ee9c865a 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -21,6 +21,11 @@ from .sql_query.mongodb import query_mongodb from .web.search import bing_search, google_search from .web.arxiv import arxiv_search +from .web.tripadvisor import ( + get_tripadvisor_location_photos, + search_tripadvisor, + get_tripadvisor_location_details +) from .web.dblp import ( dblp_search_publications, dblp_search_authors, @@ -101,6 +106,9 @@ def get_help() -> None: "openai_image_to_text", "openai_edit_image", "openai_create_image_variation", + "get_tripadvisor_location_photos", + "search_tripadvisor", + "get_tripadvisor_location_details", # to be deprecated "ServiceFactory", ] diff --git a/src/agentscope/service/web/tripadvisor.py b/src/agentscope/service/web/tripadvisor.py new file mode 100644 index 000000000..71504054f --- /dev/null +++ b/src/agentscope/service/web/tripadvisor.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +"""TripAdvisor API functions for searching and retrieving location information""" + +from agentscope.service.service_response import ServiceResponse +from agentscope.service.service_status import ServiceExecStatus +from typing import List, Any, Dict +import requests + +def get_tripadvisor_location_photos(api_key: str, location_id: str, language: str = 'en') -> ServiceResponse: + """ + Get photos for a specific location using the TripAdvisor API and return the largest one. + + Args: + api_key (`str`): + Your TripAdvisor API key. + location_id (`str`): + The location ID for the desired location. + language (`str`, optional): + The language for the response. Defaults to 'en'. + + Returns: + `ServiceResponse`: A dictionary with two variables: `status` and + `content`. The `status` variable is from the ServiceExecStatus enum, + and `content` is a dictionary containing the largest photo information + or error information, which depends on the `status` variable. + + Example: + .. code-block:: python + + result = get_tripadvisor_location_photos("your_api_key", "12345", "en") + if result.status == ServiceExecStatus.SUCCESS: + print(result.content['largest_photo']) + """ + def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Find the photo with the largest dimensions from the list of photos. + + Args: + photos (List[Dict[str, Any]]): List of photo data from TripAdvisor API. + + Returns: + Dict[str, Any]: The photo data with the largest dimensions. + """ + largest_photo_info = None + max_area = 0 + + for item in photos: + for image_type, image_info in item['images'].items(): + height = image_info['height'] + width = image_info['width'] + area = height * width + + if area > max_area: + max_area = area + largest_photo_info = { + 'url': image_info['url'], + 'height': height, + 'width': width, + 'caption': item.get('caption', ''), + 'album': item.get('album', ''), + 'published_date': item.get('published_date', ''), + 'id': item.get('id', ''), + 'source': item.get('source', {}), + 'user': item.get('user', {}) + } + + return largest_photo_info + + + url = f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/photos?language={language}&key={api_key}" + headers = { + "accept": "application/json" + } + + try: + response = requests.get(url, headers=headers) + if response.status_code == 200: + data = response.json() + largest_photo = find_largest_photo(data['data']) + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content={"largest_photo": largest_photo} + ) + else: + error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": error_detail} + ) + except Exception as e: + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": str(e)} + ) + + +def search_tripadvisor(api_key: str, query: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: + """ + Search for locations using the TripAdvisor API. + + Args: + api_key (`str`): + Your TripAdvisor API key. + query (`str`): + The search query. + language (`str`, optional): + The language for the response. Defaults to 'en'. + currency (`str`, optional): + The currency for the response. Defaults to 'USD'. + + Returns: + `ServiceResponse`: A dictionary with two variables: `status` and + `content`. The `status` variable is from the ServiceExecStatus enum, + and `content` is the JSON response from TripAdvisor API or error + information, which depends on the `status` variable. + + Example: + .. code-block:: python + + result = search_tripadvisor("your_api_key", "Paris", "en", "EUR") + if result.status == ServiceExecStatus.SUCCESS: + print(result.content) + """ + url = f"https://api.content.tripadvisor.com/api/v1/location/search?searchQuery={query}&language={language}&key={api_key}" + headers = { + "accept": "application/json" + } + + try: + response = requests.get(url, headers=headers) + if response.status_code == 200: + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=response.json() + ) + else: + error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": error_detail} + ) + except Exception as e: + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": str(e)} + ) + +def get_tripadvisor_location_details(api_key: str, location_id: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: + """ + Get details for a specific location using the TripAdvisor API. + + Args: + api_key (`str`): + Your TripAdvisor API key. + location_id (`str`): + The location ID for the desired location. + language (`str`, optional): + The language for the response. Defaults to 'en'. + currency (`str`, optional): + The currency for the response. Defaults to 'USD'. + + Returns: + `ServiceResponse`: A dictionary with two variables: `status` and + `content`. The `status` variable is from the ServiceExecStatus enum, + and `content` is the JSON response from TripAdvisor API or error + information, which depends on the `status` variable. + + Example: + .. code-block:: python + + result = get_tripadvisor_location_details("your_api_key", "12345", "en", "EUR") + if result.status == ServiceExecStatus.SUCCESS: + print(result.content) + """ + url = f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/details?language={language}¤cy={currency}&key={api_key}" + headers = { + "accept": "application/json" + } + + try: + response = requests.get(url, headers=headers) + if response.status_code == 200: + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content=response.json() + ) + else: + error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": error_detail} + ) + except Exception as e: + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": str(e)} + ) \ No newline at end of file From 1919e4830856caf5a2b2ddfc3b57b44914ab4e6a Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Thu, 11 Jul 2024 20:35:53 +0800 Subject: [PATCH 02/24] fixed formatting issue raised by pre-commit --- src/agentscope/service/__init__.py | 6 +- src/agentscope/service/web/tripadvisor.py | 165 +++++++++++++--------- 2 files changed, 101 insertions(+), 70 deletions(-) diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 4ee9c865a..74c87f5df 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -22,9 +22,9 @@ from .web.search import bing_search, google_search from .web.arxiv import arxiv_search from .web.tripadvisor import ( - get_tripadvisor_location_photos, - search_tripadvisor, - get_tripadvisor_location_details + get_tripadvisor_location_photos, + search_tripadvisor, + get_tripadvisor_location_details, ) from .web.dblp import ( dblp_search_publications, diff --git a/src/agentscope/service/web/tripadvisor.py b/src/agentscope/service/web/tripadvisor.py index 71504054f..91259c4c5 100644 --- a/src/agentscope/service/web/tripadvisor.py +++ b/src/agentscope/service/web/tripadvisor.py @@ -1,14 +1,22 @@ # -*- coding: utf-8 -*- -"""TripAdvisor API functions for searching and retrieving location information""" +"""TripAdvisor APIs for searching and retrieving location information.""" -from agentscope.service.service_response import ServiceResponse -from agentscope.service.service_status import ServiceExecStatus from typing import List, Any, Dict + import requests -def get_tripadvisor_location_photos(api_key: str, location_id: str, language: str = 'en') -> ServiceResponse: +from agentscope.service.service_response import ServiceResponse +from agentscope.service.service_status import ServiceExecStatus + + +def get_tripadvisor_location_photos( + api_key: str, + location_id: str, + language: str = "en", +) -> ServiceResponse: """ - Get photos for a specific location using the TripAdvisor API and return the largest one. + Get photos for a specific location using + the TripAdvisor API and return the largest one. Args: api_key (`str`): @@ -27,74 +35,79 @@ def get_tripadvisor_location_photos(api_key: str, location_id: str, language: st Example: .. code-block:: python - result = get_tripadvisor_location_photos("your_api_key", "12345", "en") + result = get_tripadvisor_location_photos( + "your_api_key", "12345", "en" + ) if result.status == ServiceExecStatus.SUCCESS: print(result.content['largest_photo']) """ - def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - Find the photo with the largest dimensions from the list of photos. - Args: - photos (List[Dict[str, Any]]): List of photo data from TripAdvisor API. - - Returns: - Dict[str, Any]: The photo data with the largest dimensions. - """ - largest_photo_info = None + def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: + """Find the photo with the largest + dimensions from the list of photos.""" + largest_photo_info = {} max_area = 0 for item in photos: - for image_type, image_info in item['images'].items(): - height = image_info['height'] - width = image_info['width'] + for image_info in item["images"].values(): + height = image_info["height"] + width = image_info["width"] area = height * width if area > max_area: max_area = area largest_photo_info = { - 'url': image_info['url'], - 'height': height, - 'width': width, - 'caption': item.get('caption', ''), - 'album': item.get('album', ''), - 'published_date': item.get('published_date', ''), - 'id': item.get('id', ''), - 'source': item.get('source', {}), - 'user': item.get('user', {}) + "url": image_info["url"], + "height": height, + "width": width, + "caption": item.get("caption", ""), + "album": item.get("album", ""), + "published_date": item.get("published_date", ""), + "id": item.get("id", ""), + "source": item.get("source", {}), + "user": item.get("user", {}), } return largest_photo_info - - url = f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/photos?language={language}&key={api_key}" + url = ( + f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/" + f"photos?language={language}&key={api_key}" + ) headers = { - "accept": "application/json" + "accept": "application/json", } try: response = requests.get(url, headers=headers) if response.status_code == 200: data = response.json() - largest_photo = find_largest_photo(data['data']) + largest_photo = find_largest_photo(data["data"]) return ServiceResponse( status=ServiceExecStatus.SUCCESS, - content={"largest_photo": largest_photo} - ) - else: - error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") - return ServiceResponse( - status=ServiceExecStatus.ERROR, - content={"error": error_detail} + content={"largest_photo": largest_photo}, ) + error_detail = ( + response.json() + .get("error", {}) + .get("message", f"HTTP Error: {response.status_code}") + ) + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": error_detail}, + ) except Exception as e: return ServiceResponse( status=ServiceExecStatus.ERROR, - content={"error": str(e)} + content={"error": str(e)}, ) -def search_tripadvisor(api_key: str, query: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: +def search_tripadvisor( + api_key: str, + query: str, + language: str = "en", +) -> ServiceResponse: """ Search for locations using the TripAdvisor API. @@ -105,8 +118,6 @@ def search_tripadvisor(api_key: str, query: str, language: str = 'en', currency: The search query. language (`str`, optional): The language for the response. Defaults to 'en'. - currency (`str`, optional): - The currency for the response. Defaults to 'USD'. Returns: `ServiceResponse`: A dictionary with two variables: `status` and @@ -117,13 +128,16 @@ def search_tripadvisor(api_key: str, query: str, language: str = 'en', currency: Example: .. code-block:: python - result = search_tripadvisor("your_api_key", "Paris", "en", "EUR") + result = search_tripadvisor("your_api_key", "Paris", "en") if result.status == ServiceExecStatus.SUCCESS: print(result.content) """ - url = f"https://api.content.tripadvisor.com/api/v1/location/search?searchQuery={query}&language={language}&key={api_key}" + url = ( + f"https://api.content.tripadvisor.com/api/v1/location/search?" + f"searchQuery={query}&language={language}&key={api_key}" + ) headers = { - "accept": "application/json" + "accept": "application/json", } try: @@ -131,21 +145,30 @@ def search_tripadvisor(api_key: str, query: str, language: str = 'en', currency: if response.status_code == 200: return ServiceResponse( status=ServiceExecStatus.SUCCESS, - content=response.json() - ) - else: - error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") - return ServiceResponse( - status=ServiceExecStatus.ERROR, - content={"error": error_detail} + content=response.json(), ) + error_detail = ( + response.json() + .get("error", {}) + .get("message", f"HTTP Error: {response.status_code}") + ) + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": error_detail}, + ) except Exception as e: return ServiceResponse( status=ServiceExecStatus.ERROR, - content={"error": str(e)} + content={"error": str(e)}, ) -def get_tripadvisor_location_details(api_key: str, location_id: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: + +def get_tripadvisor_location_details( + api_key: str, + location_id: str, + language: str = "en", + currency: str = "USD", +) -> ServiceResponse: """ Get details for a specific location using the TripAdvisor API. @@ -168,13 +191,18 @@ def get_tripadvisor_location_details(api_key: str, location_id: str, language: s Example: .. code-block:: python - result = get_tripadvisor_location_details("your_api_key", "12345", "en", "EUR") + result = get_tripadvisor_location_details( + "your_api_key", "12345", "en", "EUR" + ) if result.status == ServiceExecStatus.SUCCESS: print(result.content) """ - url = f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/details?language={language}¤cy={currency}&key={api_key}" + url = ( + f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/" + f"details?language={language}¤cy={currency}&key={api_key}" + ) headers = { - "accept": "application/json" + "accept": "application/json", } try: @@ -182,16 +210,19 @@ def get_tripadvisor_location_details(api_key: str, location_id: str, language: s if response.status_code == 200: return ServiceResponse( status=ServiceExecStatus.SUCCESS, - content=response.json() - ) - else: - error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") - return ServiceResponse( - status=ServiceExecStatus.ERROR, - content={"error": error_detail} + content=response.json(), ) + error_detail = ( + response.json() + .get("error", {}) + .get("message", f"HTTP Error: {response.status_code}") + ) + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": error_detail}, + ) except Exception as e: return ServiceResponse( status=ServiceExecStatus.ERROR, - content={"error": str(e)} - ) \ No newline at end of file + content={"error": str(e)}, + ) From 958d4f1e578761ee0cd7b8cd72b9d1867b9f98e0 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 12 Jul 2024 03:17:57 +0800 Subject: [PATCH 03/24] improved logging --- src/agentscope/service/web/tripadvisor.py | 32 +++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/agentscope/service/web/tripadvisor.py b/src/agentscope/service/web/tripadvisor.py index 91259c4c5..8b1845750 100644 --- a/src/agentscope/service/web/tripadvisor.py +++ b/src/agentscope/service/web/tripadvisor.py @@ -2,9 +2,8 @@ """TripAdvisor APIs for searching and retrieving location information.""" from typing import List, Any, Dict - +from loguru import logger import requests - from agentscope.service.service_response import ServiceResponse from agentscope.service.service_status import ServiceExecStatus @@ -78,11 +77,18 @@ def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: "accept": "application/json", } + logger.info(f"Requesting photos for location ID {location_id}") + try: response = requests.get(url, headers=headers) + logger.info( + f"Received response with status code {response.status_code}", + ) + if response.status_code == 200: data = response.json() largest_photo = find_largest_photo(data["data"]) + logger.info("Successfully retrieved the largest photo") return ServiceResponse( status=ServiceExecStatus.SUCCESS, content={"largest_photo": largest_photo}, @@ -92,11 +98,13 @@ def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: .get("error", {}) .get("message", f"HTTP Error: {response.status_code}") ) + logger.error(f"Error in response: {error_detail}") return ServiceResponse( status=ServiceExecStatus.ERROR, content={"error": error_detail}, ) except Exception as e: + logger.exception("Exception occurred while requesting location photos") return ServiceResponse( status=ServiceExecStatus.ERROR, content={"error": str(e)}, @@ -140,9 +148,16 @@ def search_tripadvisor( "accept": "application/json", } + logger.info(f"Searching for locations with query '{query}'") + try: response = requests.get(url, headers=headers) + logger.info( + f"Received response with status code {response.status_code}", + ) + if response.status_code == 200: + logger.info("Successfully retrieved search results") return ServiceResponse( status=ServiceExecStatus.SUCCESS, content=response.json(), @@ -152,11 +167,13 @@ def search_tripadvisor( .get("error", {}) .get("message", f"HTTP Error: {response.status_code}") ) + logger.error(f"Error in response: {error_detail}") return ServiceResponse( status=ServiceExecStatus.ERROR, content={"error": error_detail}, ) except Exception as e: + logger.exception("Exception occurred while searching for locations") return ServiceResponse( status=ServiceExecStatus.ERROR, content={"error": str(e)}, @@ -205,9 +222,16 @@ def get_tripadvisor_location_details( "accept": "application/json", } + logger.info(f"Requesting details for location ID {location_id}") + try: response = requests.get(url, headers=headers) + logger.info( + f"Received response with status code {response.status_code}", + ) + if response.status_code == 200: + logger.info("Successfully retrieved location details") return ServiceResponse( status=ServiceExecStatus.SUCCESS, content=response.json(), @@ -217,11 +241,15 @@ def get_tripadvisor_location_details( .get("error", {}) .get("message", f"HTTP Error: {response.status_code}") ) + logger.error(f"Error in response: {error_detail}") return ServiceResponse( status=ServiceExecStatus.ERROR, content={"error": error_detail}, ) except Exception as e: + logger.exception( + "Exception occurred while requesting location details", + ) return ServiceResponse( status=ServiceExecStatus.ERROR, content={"error": str(e)}, From abf79bb1324806adf23a8ec28f5955e2f86c3d26 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:45:39 +0800 Subject: [PATCH 04/24] created two geoguessr games based on tripadvisor api tool function --- .../geoguesser_neurosymbolic.py | 457 ++++++++++++++++++ examples/game_geoguessr/geoguessr.py | 259 ++++++++++ 2 files changed, 716 insertions(+) create mode 100644 examples/game_geoguessr/geoguesser_neurosymbolic.py create mode 100644 examples/game_geoguessr/geoguessr.py diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py new file mode 100644 index 000000000..5d9f66c9b --- /dev/null +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -0,0 +1,457 @@ +from IPython.display import Image, display +from IPython.display import clear_output, display +import requests +from io import BytesIO +from PIL import Image as PILImage + +import logging +import requests +from typing import Optional +from agentscope.service.service_response import ServiceResponse, ServiceExecStatus +from agentscope.service.service_toolkit import ServiceToolkit +import agentscope +from agentscope.message import Msg +from agentscope.agents.dialog_agent import DialogAgent as BaseDialogAgent +from agentscope.agents import DialogAgent +import json +import ast + +from typing import Dict, Any, List +from agentscope.parsers import ParserBase +from agentscope.models import ModelResponse +import re +import os + +class LocationMatcherParser(ParserBase): + """A parser to match locations in natural language text against a predefined list of locations.""" + + def __init__(self, locations): + """ + Initialize the parser with a list of location dictionaries. + + Args: + locations (list): A list of dictionaries, each containing location information. + """ + self.locations = locations + + def parse(self, response: ModelResponse) -> ModelResponse: + """ + Parse the response text to find matches with the predefined locations. + + Args: + response (ModelResponse): The response object containing the text to parse. + + Returns: + ModelResponse: The response object with the parsed result added. + """ + text = response.text + matches = [] + + for location in self.locations: + if re.search(r'\b' + re.escape(location['name']) + r'\b', text, re.IGNORECASE): + matches.append(location) + + response.parsed = matches + return response + +import agentscope +agentscope.init( + # ... + project="xxx", + name="xxx", + studio_url="http://127.0.0.1:5000" # The URL of AgentScope Studio +) +# Setup logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +from agentscope.service import ( + get_tripadvisor_location_photos, + search_tripadvisor, + get_tripadvisor_location_details +) + +# Initialize the ServiceToolkit and register the TripAdvisor API functions +service_toolkit = ServiceToolkit() +service_toolkit.add(search_tripadvisor, api_key="") # Replace with your actual TripAdvisor API key +service_toolkit.add(get_tripadvisor_location_details, api_key="") # Replace with your actual TripAdvisor API key +service_toolkit.add(get_tripadvisor_location_photos, api_key="") # Replace with your actual TripAdvisor API key + + +class ExtendedDialogAgent(BaseDialogAgent): + def __init__(self, name: str, sys_prompt: str, model_config_name: str, use_memory: bool = True, memory_config: Optional[dict] = None): + super().__init__(name, sys_prompt, model_config_name, use_memory, memory_config) + self.service_toolkit = service_toolkit + self.current_location = None + self.current_details = None + self.game_state = "start" + + def parse_service_response(self, response: str) -> dict: + """ + Parse the service response string and extract the JSON content. + Args: + response (str): The response string from the service call. + Returns: + dict: The parsed JSON content. + """ + result = {} + lines = response.split('\n') + for line in lines: + if '[STATUS]:' in line: + status = line.split('[STATUS]:')[-1].strip() + result['status'] = ServiceExecStatus.SUCCESS if status == 'SUCCESS' else ServiceExecStatus.ERROR + if '[RESULT]:' in line: + json_str = line.split('[RESULT]:')[-1].strip() + result['content'] = ast.literal_eval(json_str) + return result + + def propose_location(self): + messages = [ + {"role": "system", "content": "You are a game master for a geography guessing game. Propose an interesting location as for the player to guess. Diversify your proposal and give less known ones."}, + {"role": "user", "content": "Propose an interesting location for the player to guess. Only output your final proposed location."} + ] + response = self.model(messages).text.strip() + return response + + def search_and_select_location(self, proposed_location): + response_str = self.service_toolkit.parse_and_call_func( + [{"name": "search_tripadvisor", "arguments": {"query": proposed_location}}] + ) + result = self.parse_service_response(response_str) + if result['status'] == ServiceExecStatus.SUCCESS and result['content']['data']: + locations = result['content']['data'] + if len(locations) > 1: + messages = [ + {"role": "system", "content": "You are selecting the most appropriate location from a list of search results."}, + {"role": "user", "content": f"Select the location that best matches '{proposed_location}' from this list:\n" + + "\n".join([f"{i+1}. {loc['name']}, {loc.get('address_obj', {}).get('city', 'Unknown City')}, {loc.get('address_obj', {}).get('country', 'Unknown Country')}" for i, loc in enumerate(locations)]) + + "\nRespond with only the number of your selection."} + ] + selection_response = self.model(messages).text.strip() + try: + # Try to extract the number from the response + selection = int(''.join(filter(str.isdigit, selection_response))) - 1 + if 0 <= selection < len(locations): + return locations[selection] + else: + # If the extracted number is out of range, return the first location + return locations[0] + except ValueError: + # If no number can be extracted, return the first location + return locations[0] + else: + return locations[0] + return None + + + def get_location_details(self, location_id): + response_str = self.service_toolkit.parse_and_call_func( + [{"name": "get_tripadvisor_location_details", "arguments": {"location_id": location_id}}] + ) + return self.parse_service_response(response_str) + + def get_location_photos(self, location_id: str) -> dict: + """ + Get the largest photo for a specific location. + + Args: + location_id (str): The location ID to get photos for. + + Returns: + dict: The largest photo information including the URL. + """ + logger.info(f"Calling TripAdvisor API for location photos: {location_id}") + response_str = self.service_toolkit.parse_and_call_func( + [{"name": "get_tripadvisor_location_photos", "arguments": {"location_id": location_id}}] + ) + logger.info(f"TripAdvisor location photos result: {response_str}") + result = self.parse_service_response(response_str) + return result['content']['largest_photo'] if result['status'] == ServiceExecStatus.SUCCESS else None + + + def display_photo(self, location_id): + photos = self.get_location_photos(location_id) + if photos: + try: + image_url = photos['url'] + response = requests.get(image_url) + img = PILImage.open(BytesIO(response.content)) + display(img) + return True + except Exception as e: + logger.error(f"Error displaying image: {str(e)}") + return False + + # def check_guess(self, user_guess, ancestors): + # for ancestor in ancestors: + # if user_guess.lower() in ancestor['name'].lower(): + # return ancestor['level'], ancestor['name'] + # return None, None + + def check_guess_location(self, user_guess, location): + + messages = [ + {"role": "system", "content": '''You are the gamemaster of a geoguessr game. Check if the player's guess: '''+ str(user_guess)+''' means the same place/location as '''+ str(location)+'''. If yes, return 'True'. Else, return 'False'. Only output one of the two options given and nothing else.'''}, + {"role": "user", "content": user_guess} + ] + + response = self.model(messages).text.strip() + logger.info(f"check_guess: {response}") + return response + + + + def check_guess_ancestors(self, user_guess, ancestors): + # parser = LocationMatcherParser(ancestors) + # response = ModelResponse(text="This location is an indigenous village nestled within a picturesque canyon in the southwestern part of the United States. It's accessible via a trail that winds through the rugged landscape of Arizona.") + + # parsed_response = parser.parse(response) + + matches = [] + + for location in ancestors: + if re.search(r'\b' + re.escape(location['name']) + r'\b', user_guess, re.IGNORECASE): + matches.append(location) + if not matches: + return 'None','None','False' + else: + messages = [ + {"role": "system", "content": '''Check if '''+ str(matches)+''' include the smallest level in '''+ str(ancestors)+''' based on their respective levels. If yes, return the smallest matched name and the corresponding level as 'level,name,True'. Else, return 'False'. Note that the placeholders 'level', 'name' in the output are to be replaced by the actual values. Only output one of the two options given and nothing else.'''}, + {"role": "user", "content": user_guess} + ] + # messages = [ + # {"role": "system", "content": '''You're a game master for a geography guessing game. Check user's guess if any part of the guess (including Country) matches any name at any level in ancestors: '''+ str(ancestors)+'''. If doesn't match at all levels, return 'None,None,False'. + # If it matches but the smallest matched name's corresponding level is not the smallest level available in ancestors, return the smallest **matched** name and the corresponding level as 'level,name,False'. + # If it matches and the smallest matched name's corresponding level is the smallest level available in ancestors, return the smallest matched name and the corresponding level as 'level,name,True'. + # Only output one of the three options given and nothing else.'''}, + # {"role": "user", "content": user_guess} + # ] + response = self.model(messages).text.strip() + if 'True' in response: + logger.info(f"check_guess: {response}") + response = response.split(',') + return response[0],response[1],response[2] + else: + messages = [ + {"role": "system", "content": '''Return the smallest name based on their respective 'level' values and the corresponding 'level' values in '''+ str(matches)+''' as 'level,name,False'. Note that the placeholders 'level', 'name' in the output are to be replaced by the actual values. Only output in the given format and nothing else.'''}, + {"role": "user", "content": user_guess} + ] + response = self.model(messages).text.strip() + logger.info(f"check_guess: {response}") + response = response.split(',') + return response[0],response[1],response[2] + + def save_image_from_url(self, url, save_path): + try: + os.makedirs(save_path, exist_ok=True) + # Send a HTTP request to the URL + response = requests.get(url, stream=True) + response.raise_for_status() # Check if the request was successful + + # Get the file name from the URL + file_name = os.path.basename(url) + file_name = 'image'+os.path.splitext(file_name)[1] + # Full path to save the image + full_path = os.path.join(save_path, file_name) + + # Open a local file with write-binary mode + with open(full_path, 'wb') as file: + for chunk in response.iter_content(chunk_size=8192): + file.write(chunk) + + print(f"Image saved successfully at {full_path}") + return full_path + except Exception as e: + print(f"An error occurred: {e}") + + def get_hint(self, details): + messages = [ + {"role": "system", "content": "You're a game master for a geography guessing game."}, + {"role": "user", "content": f"give the user some hint about the location based on the info from {details}. Don't make it too obvious."} + ] + response = self.model(messages).text.strip() + logger.info(f"Hint: {response}") + return response + + + def handle_input(self, user_input: dict) -> dict: + query = user_input['text'] + + if self.game_state == "start": + photo_displayed = False + while not photo_displayed: + proposed_location = self.propose_location() + self.current_location = self.search_and_select_location(proposed_location) + print('self.current_location: ', self.current_location) + if not self.current_location: + return {"text": "I'm sorry, I couldn't find a suitable location. Let's try again."} + + self.current_details = self.get_location_details(self.current_location['location_id']) + if self.current_details['status'] != ServiceExecStatus.SUCCESS: + return {"text": "I'm having trouble getting details for this location. Let's try again."} + ancestors = self.current_details['content'].get('ancestors', []) + print('ancestors: ', ancestors) + + # clear_output(wait=True) + display(None) + photo_displayed = self.display_photo(self.current_location['location_id']) + photos = self.get_location_photos(self.current_location['location_id']) + + response = "Let's play a geography guessing game! I've chosen a location and displayed an image of it. " + # response += f"![image]({photos['url']})" + response += " Can you guess which country, state, region, city, or municipality this location is in?" + self.game_state = "guessing" + image_path = self.save_image_from_url(photos['url'], save_path = "./images") + + return [{"text": response}, {"image": image_path}] + + elif self.game_state == "guessing": + if self.check_guess_location(query.lower(), self.current_location['name'].lower()) == 'True': + self.game_state = "end" + return {"text": f"Congratulations! You've guessed correctly. The location is indeed in {self.current_location['name']}."} + ancestors = self.current_details['content'].get('ancestors', []) + level, correct_name, is_smallest = self.check_guess_ancestors(query, ancestors) + + if level != 'None': + if is_smallest == 'True': + self.game_state = "end" + return {"text": f"Congratulations! You've guessed correctly. The location is indeed in {level}: {correct_name}."} + else: + return {"text": f"Good guess! {level}: {correct_name} is correct, but can you be more specific? Try guessing a smaller region or city."} + else: + hint = self.get_hint(self.current_details['content']) + return {"text": f"I'm sorry, that's not correct. Here's a hint: {hint} Try guessing again!"} + + else: + return {"text": "I'm sorry, I don't understand. Let's start a new game!"} + +# Initialize AgentScope and run +def main() -> None: + """A GeoGuessr-like game demo""" + + agentscope.init( + model_configs=[ + { + "model_type": "openai_chat", + "config_name": "gpt-3.5-turbo", + "model_name": "gpt-3.5-turbo", + "api_key": "", # Load from env if not provided + "generate_args": { + "temperature": 0.5, + }, + }, + { + "config_name": "dashscope_chat-qwen-max", + "model_type": "dashscope_chat", + "model_name": "qwen-max-1201", + "api_key": "", + "generate_args": { + "temperature": 0.1 + } + }, + { + "config_name": "dashscope_multimodal-qwen-vl-max", + "model_type": "dashscope_multimodal", + "model_name": "qwen-vl-max", + "api_key": "", + "generate_args": { + "temperature": 0.01 + } + }, + { + "config_name": "gpt-4o_config", + "model_type": "openai_chat", + "model_name": "gpt-4o", + "api_key": "", + "generate_args": { + "temperature": 0.8, + }, + } + ], + ) + + # Init the dialog agent + gamemaster_agent = ExtendedDialogAgent( + name="GeoGuessr Gamemaster", + sys_prompt="You're a game master for a geography guessing game.", + model_config_name="gpt-4o_config", + use_memory=True, + ) + + player_agent = DialogAgent( + name="player", + sys_prompt='''You're a player in a geoguessr-like turn-based game. Upon getting an image from the gamemaster, you are supposed to guess where is the place shown in the image. Your guess can be a country, + a state, a region, a city, etc., but try to be as precise as possoble. If your answer is not correct, try again based on the hint given by the gamemaster.''', + model_config_name="dashscope_multimodal-qwen-vl-max", # replace by your model config name + ) + + # Start the game + x = None + while gamemaster_agent.game_state != 'guessing': + + response = gamemaster_agent.handle_input({"text": "Let's start the game"}) + x = Msg("GeoGuessr Gamemaster", response[0]['text'], url=response[1]['image']) + + # x = Msg("GeoGuessr Gamemaster", response) + gamemaster_agent.speak(x) + print("Game Master:", response[0]['text']) + + # Main game loop + + while gamemaster_agent.game_state != 'end': + # Clear previous output + # clear_output(wait=True) + + # Display the image and game master's text + # if gamemaster_agent.game_state == "guessing": + # gamemaster_agent.display_photo(gamemaster_agent.current_location['location_id']) + # print("Game Master:", response['text']) + + # Force display update + display(None) + + # user_input = input("Your guess: ") + x = player_agent(x) + if 'quit' in x['content'].lower(): + print("Thanks for playing!") + break + response = gamemaster_agent.handle_input({"text": x['content']}) + x = Msg("GeoGuessr Gamemaster", response['text']) + gamemaster_agent.speak(x) + print("Game Master:", response['text']) + + + #pve + + # # Start the game + # x = None + # while gamemaster_agent.game_state != 'guessing': + + # response = gamemaster_agent.handle_input({"text": "Let's start the game"}) + # x = Msg("GeoGuessr Gamemaster", response['text']) + + # print("Game Master:", response['text']) + + # # Main game loop + + # while gamemaster_agent.game_state != 'end': + # # Clear previous output + # # clear_output(wait=True) + + # # Display the image and game master's text + # # if gamemaster_agent.game_state == "guessing": + # # gamemaster_agent.display_photo(gamemaster_agent.current_location['location_id']) + # print("Game Master:", response['text']) + + # # Force display update + # display(None) + + # user_input = input("Your guess: ") + # if user_input.lower() == 'quit': + # print("Thanks for playing!") + # break + # response = gamemaster_agent.handle_input({"text": user_input}) + # print("Game Master:", response['text']) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/game_geoguessr/geoguessr.py b/examples/game_geoguessr/geoguessr.py new file mode 100644 index 000000000..8a794d790 --- /dev/null +++ b/examples/game_geoguessr/geoguessr.py @@ -0,0 +1,259 @@ +import requests +from typing import Dict, Any, List +from agentscope.service import ServiceResponse, ServiceExecStatus +from agentscope.agents import DialogAgent +import agentscope +agentscope.init( + # ... + project="xxx", + name="xxx", + studio_url="http://127.0.0.1:5000" # The URL of AgentScope Studio +) + +YOUR_MODEL_CONFIGURATION_NAME = "dashscope_chat-qwen-max" + +# YOUR_MODEL_CONFIGURATION = { +# "model_type": "openai_chat", +# "config_name": "gpt-3.5-turbo", +# "model_name": "gpt-3.5-turbo", +# "api_key": "", # Load from env if not provided +# "generate_args": { +# "temperature": 0.7, +# }, +# } + +YOUR_MODEL_CONFIGURATION = [{ + "config_name": "dashscope_chat-qwen-max", + "model_type": "dashscope_chat", + "model_name": "qwen-max", + "api_key": "", + "generate_args": { + "temperature": 0.1 + } +}, + +{ + "config_name": "dashscope_multimodal-qwen-vl-max", + "model_type": "dashscope_multimodal", + "model_name": "qwen-vl-max", + "api_key": "", + "generate_args": { + "temperature": 0.2 + } +},] + +from agentscope.service import ( + get_tripadvisor_location_photos, + search_tripadvisor, + get_tripadvisor_location_details +) + +# def get_tripadvisor_location_photos(api_key: str, location_id: str, language: str = 'en') -> ServiceResponse: +# """ +# Get photos for a specific location using the TripAdvisor API and return the largest one. + +# Args: +# api_key (str): Your TripAdvisor API key. +# location_id (str): The location ID for the desired location. +# language (str, optional): The language for the response. Defaults to 'en'. + +# Returns: +# ServiceResponse: Contains the status and the response content with the largest photo. +# """ +# def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: +# """ +# Find the photo with the largest dimensions from the list of photos. + +# Args: +# photos (List[Dict[str, Any]]): List of photo data from TripAdvisor API. + +# Returns: +# Dict[str, Any]: The photo data with the largest dimensions. +# """ +# largest_photo_info = None +# max_area = 0 + +# for item in photos: +# for image_type, image_info in item['images'].items(): +# height = image_info['height'] +# width = image_info['width'] +# area = height * width + +# if area > max_area: +# max_area = area +# largest_photo_info = { +# 'url': image_info['url'], +# 'height': height, +# 'width': width, +# 'caption': item.get('caption', ''), +# 'album': item.get('album', ''), +# 'published_date': item.get('published_date', ''), +# 'id': item.get('id', ''), +# 'source': item.get('source', {}), +# 'user': item.get('user', {}) +# } + +# return largest_photo_info + + +# url = f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/photos?language={language}&key={api_key}" +# headers = { +# "accept": "application/json" +# } + +# try: +# response = requests.get(url, headers=headers) +# if response.status_code == 200: +# data = response.json() +# largest_photo = find_largest_photo(data['data']) +# return ServiceResponse( +# status=ServiceExecStatus.SUCCESS, +# content={"largest_photo": largest_photo} +# ) +# else: +# error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") +# return ServiceResponse( +# status=ServiceExecStatus.ERROR, +# content={"error": error_detail} +# ) +# except Exception as e: +# return ServiceResponse( +# status=ServiceExecStatus.ERROR, +# content={"error": str(e)} +# ) + + + +# # Define the TripAdvisor API query function +# def search_tripadvisor(api_key: str, query: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: +# """ +# Search for locations using the TripAdvisor API. + +# Args: +# api_key (str): Your TripAdvisor API key. +# query (str): The search query. +# language (str, optional): The language for the response. Defaults to 'en'. +# currency (str, optional): The currency for the response. Defaults to 'USD'. + +# Returns: +# ServiceResponse: Contains the status and the response content. +# """ +# url = f"https://api.content.tripadvisor.com/api/v1/location/search?searchQuery={query}&language={language}&key={api_key}" +# headers = { +# "accept": "application/json" +# } + +# try: +# response = requests.get(url, headers=headers) +# if response.status_code == 200: +# return ServiceResponse( +# status=ServiceExecStatus.SUCCESS, +# content=response.json() +# ) +# else: +# error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") +# return ServiceResponse( +# status=ServiceExecStatus.ERROR, +# content={"error": error_detail} +# ) +# except Exception as e: +# return ServiceResponse( +# status=ServiceExecStatus.ERROR, +# content={"error": str(e)} +# ) + +# # Define the TripAdvisor location details query function +# def get_tripadvisor_location_details(api_key: str, location_id: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: +# """ +# Get details for a specific location using the TripAdvisor API. + +# Args: +# api_key (str): Your TripAdvisor API key. +# location_id (str): The location ID for the desired location. +# language (str, optional): The language for the response. Defaults to 'en'. +# currency (str, optional): The currency for the response. Defaults to 'USD'. + +# Returns: +# ServiceResponse: Contains the status and the response content. +# """ +# url = f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/details?language={language}¤cy={currency}&key={api_key}" +# headers = { +# "accept": "application/json" +# } + +# try: +# response = requests.get(url, headers=headers) +# if response.status_code == 200: +# return ServiceResponse( +# status=ServiceExecStatus.SUCCESS, +# content=response.json() +# ) +# else: +# error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") +# return ServiceResponse( +# status=ServiceExecStatus.ERROR, +# content={"error": error_detail} +# ) +# except Exception as e: +# return ServiceResponse( +# status=ServiceExecStatus.ERROR, +# content={"error": str(e)} +# ) + +from agentscope.service import ServiceToolkit + +# Initialize the ServiceToolkit and register the TripAdvisor API functions +service_toolkit = ServiceToolkit() +service_toolkit.add(search_tripadvisor, api_key="") # Replace with your actual TripAdvisor API key +service_toolkit.add(get_tripadvisor_location_details, api_key="") # Replace with your actual TripAdvisor API key +service_toolkit.add(get_tripadvisor_location_photos, api_key="") # Replace with your actual TripAdvisor API key +import json +print(json.dumps(service_toolkit.json_schemas, indent=4)) + +from agentscope.agents import ReActAgent +import agentscope + +agentscope.init(model_configs=YOUR_MODEL_CONFIGURATION) + +gamemaster_agent = ReActAgent( + name="gamemaster", + sys_prompt='''You are the gamemaster of a geoguessr-like game. You interacts with the player following the procedures below: +1. You propose a location (anywhere on earth, the mroe random and low-profile the better, diversify your proposal), then uses the tool `search_tripadvisor` to get its `location_id`; if multiple places are returned, you choose the one that matches what you proposed the most. +2. You use the method `get_tripadvisor_location_photos` to get the url of the image about this location, display the url as part of your output under 'speeak' (use '![image]' as alt text do not use the real name of the lcoation), and asks the player to guess which country/state/region/city/municipality this location is in. If the url is not available for this particular location, repeat step 1 and 2 until a valid image url is found. +3. You use the tool `get_tripadvisor_location_details` to get this location's details. +4. If the player's answer matches exactly the locaiton name correspond to the most precise `level` in `ancestors` in the returned details from step 2, they have won the game and game is over. Note that it is sufficient for the player to just give the exact location name to win the game. Congratulate the player. If the player's answer corresponds to other `level` in `ancestors` instead of the most precise one, encourage the player to give a more precise guess. if the player's answer does not matches the location name, nor any of the values of `name` in `ancestors` in the returned details from step 2, give the player a hint from the details from step 2 that is not too revealing, and ask the player to guess again. +Under no circumstances should you reveal the name of the location or of any object in the image before the player guesses it correctly. +When the game is over, append 'exit = True' to your last output.''', + model_config_name="dashscope_chat-qwen-max", + service_toolkit=service_toolkit, + verbose=False, # set verbose to True to show the reasoning process +) + +player_agent = DialogAgent( + name="player", + sys_prompt='''You're a player in a geoguessr-like turn-based game. Upon getting the url of an image from the gamemaster, you are supposed to guess where is the place shown in the image. Your guess can be a country, + a state, a region, a city, etc., but try to be as precise as possoble. If your answer is not correct, try again based on the hint given by the gamemaster.''', + model_config_name="dashscope_multimodal-qwen-vl-max", # replace by your model config name + ) + +# print("#"*80) +# print(agent.sys_prompt) +# print("#"*80) + +from agentscope.agents import UserAgent + +user = UserAgent(name="User") +from agentscope.message import Msg +x = None +# x = Msg("Bob", "What about this picture I took?", url="https://media-cdn.tripadvisor.com/media/photo-w/1a/9e/7f/9d/eiffeltoren.jpg") +# gamemaster_agent.speak(x) +while True: + x = gamemaster_agent(x) + # x['content'].pop('thought') + # x['url'] = x['content']['image_url'] + if "exit" in x.content or "congratulation" in x.content.lower(): + break + x = player_agent(x) + # x = user(x) + + From 0fbc9a71aeff033c4f97cda83e9c415bc84e1882 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Thu, 1 Aug 2024 20:49:45 +0800 Subject: [PATCH 05/24] added more details in docstrings --- src/agentscope/service/web/tripadvisor.py | 244 +++++++++++++++++----- 1 file changed, 195 insertions(+), 49 deletions(-) diff --git a/src/agentscope/service/web/tripadvisor.py b/src/agentscope/service/web/tripadvisor.py index 8b1845750..c58400350 100644 --- a/src/agentscope/service/web/tripadvisor.py +++ b/src/agentscope/service/web/tripadvisor.py @@ -1,73 +1,104 @@ # -*- coding: utf-8 -*- """TripAdvisor APIs for searching and retrieving location information.""" -from typing import List, Any, Dict from loguru import logger import requests from agentscope.service.service_response import ServiceResponse from agentscope.service.service_status import ServiceExecStatus +# pylint: disable=line-too-long def get_tripadvisor_location_photos( api_key: str, location_id: str, language: str = "en", ) -> ServiceResponse: """ - Get photos for a specific location using - the TripAdvisor API and return the largest one. + Retrieve photos for a specific location using the TripAdvisor API. Args: api_key (`str`): Your TripAdvisor API key. location_id (`str`): - The location ID for the desired location. + The ID of the location for which to retrieve photos. language (`str`, optional): The language for the response. Defaults to 'en'. Returns: `ServiceResponse`: A dictionary with two variables: `status` and `content`. The `status` variable is from the ServiceExecStatus enum, - and `content` is a dictionary containing the largest photo information - or error information, which depends on the `status` variable. + and `content` is the JSON response from TripAdvisor API or error + information, which depends on the `status` variable. + + If successful, the `content` will be a dictionary + with the following structure: + { + 'photo_data': { + 'data': [ + { + 'id': int, + 'is_blessed': bool, + 'caption': str, + 'published_date': str, + 'images': { + 'thumbnail': {'height': int, + 'width': int, 'url': str}, + 'small': {'height': int, 'width': int, 'url': str}, + 'medium': {'height': int, + 'width': int, 'url': str}, + 'large': {'height': int, 'width': int, 'url': str}, + 'original': {'height': int, + 'width': int, 'url': str} + }, + 'album': str, + 'source': {'name': str, 'localized_name': str}, + 'user': {'username': str} + }, + ... + ] + } + } + Each item in the 'data' list represents + a photo associated with the location. Example: .. code-block:: python - result = get_tripadvisor_location_photos( - "your_api_key", "12345", "en" - ) + result = get_tripadvisor_location_photos("your_api_key", "123456", "en") # noqa: E501 if result.status == ServiceExecStatus.SUCCESS: - print(result.content['largest_photo']) - """ - - def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: - """Find the photo with the largest - dimensions from the list of photos.""" - largest_photo_info = {} - max_area = 0 - - for item in photos: - for image_info in item["images"].values(): - height = image_info["height"] - width = image_info["width"] - area = height * width - - if area > max_area: - max_area = area - largest_photo_info = { - "url": image_info["url"], - "height": height, - "width": width, - "caption": item.get("caption", ""), - "album": item.get("album", ""), - "published_date": item.get("published_date", ""), - "id": item.get("id", ""), - "source": item.get("source", {}), - "user": item.get("user", {}), - } + print(result.content) - return largest_photo_info + Example of successful `content`: + { + 'photo_data': { + 'data': [ + { + 'id': 215321638, + 'is_blessed': False, + 'caption': '', + 'published_date': '2016-09-04T20:40:14.284Z', + 'images': { + 'thumbnail': {'height': 50, 'width': 50, + 'url': 'https://media-cdn.tripadvisor.com/media/photo-t/0c/d5/8c/26/photo0jpg.jpg'}, # noqa: E501 + 'small': {'height': 150, 'width': 150, + 'url': 'https://media-cdn.tripadvisor.com/media/photo-l/0c/d5/8c/26/photo0jpg.jpg'}, # noqa: E501 + 'medium': {'height': 188, 'width': 250, + 'url': 'https://media-cdn.tripadvisor.com/media/photo-f/0c/d5/8c/26/photo0jpg.jpg'}, # noqa: E501 + 'large': {'height': 413, 'width': 550, + 'url': 'https://media-cdn.tripadvisor.com/media/photo-s/0c/d5/8c/26/photo0jpg.jpg'}, # noqa: E501 + 'original': {'height': 1920, 'width': 2560, + 'url': 'https://media-cdn.tripadvisor.com/media/photo-c/2560x500/0c/d5/8c/26/photo0jpg.jpg'} # noqa: E501 + }, + 'album': 'Other', + 'source': {'name': 'Traveler', + 'localized_name': 'Traveler'}, + 'user': {'username': 'EvaFalleth'} + }, + # ... more photo entries ... + ] + } + } + """ url = ( f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/" @@ -86,12 +117,10 @@ def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: ) if response.status_code == 200: - data = response.json() - largest_photo = find_largest_photo(data["data"]) - logger.info("Successfully retrieved the largest photo") + logger.info("Successfully retrieved the photo") return ServiceResponse( status=ServiceExecStatus.SUCCESS, - content={"largest_photo": largest_photo}, + content=response.json(), ) error_detail = ( response.json() @@ -133,12 +162,66 @@ def search_tripadvisor( and `content` is the JSON response from TripAdvisor API or error information, which depends on the `status` variable. + If successful, the `content` will be a + dictionary with the following structure: + { + 'data': [ + { + 'location_id': str, + 'name': str, + 'address_obj': { + 'street1': str, + 'street2': str, + 'city': str, + 'state': str, + 'country': str, + 'postalcode': str, + 'address_string': str + } + }, + ... + ] + } + Each item in the 'data' list represents + a location matching the search query. + Example: .. code-block:: python - result = search_tripadvisor("your_api_key", "Paris", "en") + result = search_tripadvisor("your_api_key", "Socotra", "en") if result.status == ServiceExecStatus.SUCCESS: print(result.content) + + Example of successful `content`: + { + 'data': [ + { + 'location_id': '574818', + 'name': 'Socotra Island', + 'address_obj': { + 'street2': '', + 'city': 'Aden', + 'country': 'Yemen', + 'postalcode': '', + 'address_string': 'Aden Yemen' + } + }, + { + 'location_id': '25395815', + 'name': 'Tour Socotra', + 'address_obj': { + 'street1': '20th Street', + 'city': 'Socotra Island', + 'state': 'Socotra Island', + 'country': 'Yemen', + 'postalcode': '111', + 'address_string': + '20th Street, Socotra Island 111 Yemen' + } + }, + # ... more results ... + ] + } """ url = ( f"https://api.content.tripadvisor.com/api/v1/location/search?" @@ -187,17 +270,17 @@ def get_tripadvisor_location_details( currency: str = "USD", ) -> ServiceResponse: """ - Get details for a specific location using the TripAdvisor API. + Get detailed information about a specific location using the TripAdvisor API. Args: api_key (`str`): Your TripAdvisor API key. location_id (`str`): - The location ID for the desired location. + The unique identifier for the location. language (`str`, optional): The language for the response. Defaults to 'en'. currency (`str`, optional): - The currency for the response. Defaults to 'USD'. + The currency for pricing information. Defaults to 'USD'. Returns: `ServiceResponse`: A dictionary with two variables: `status` and @@ -205,14 +288,77 @@ def get_tripadvisor_location_details( and `content` is the JSON response from TripAdvisor API or error information, which depends on the `status` variable. + If successful, the `content` will be a dictionary with detailed information + about the location, including name, address, ratings, reviews, and more. + Example: .. code-block:: python - result = get_tripadvisor_location_details( - "your_api_key", "12345", "en", "EUR" - ) + result = get_tripadvisor_location_details("your_api_key", "574818", "en", "USD") if result.status == ServiceExecStatus.SUCCESS: print(result.content) + + Example of successful `content`: + { + 'location_id': '574818', + 'name': 'Socotra Island', + 'web_url': 'https://www.tripadvisor.com/Attraction_Review-g298087-d574818-Reviews-Socotra_Island-Aden.html?m=66827', + 'address_obj': { + 'street2': '', + 'city': 'Aden', + 'country': 'Yemen', + 'postalcode': '', + 'address_string': 'Aden Yemen' + }, + 'ancestors': [ + {'level': 'City', 'name': 'Aden', 'location_id': '298087'}, + {'level': 'Country', 'name': 'Yemen', 'location_id': '294014'} + ], + 'latitude': '12.46342', + 'longitude': '53.82374', + 'timezone': 'Asia/Aden', + 'write_review': 'https://www.tripadvisor.com/UserReview-g298087-d574818-Socotra_Island-Aden.html?m=66827', + 'ranking_data': { + 'geo_location_id': '298087', + 'ranking_string': '#1 of 7 things to do in Aden', + 'geo_location_name': 'Aden', + 'ranking_out_of': '7', + 'ranking': '1' + }, + 'rating': '5.0', + 'rating_image_url': 'https://www.tripadvisor.com/img/cdsi/img2/ratings/traveler/5.0-66827-5.svg', + 'num_reviews': '62', + 'review_rating_count': {'1': '1', '2': '0', '3': '1', '4': '1', '5': '59'}, + 'photo_count': '342', + 'see_all_photos': 'https://www.tripadvisor.com/Attraction_Review-g298087-d574818-m66827-Reviews-Socotra_Island-Aden.html#photos', # noqa: E501 + 'category': {'name': 'attraction', 'localized_name': 'Attraction'}, + 'subcategory': [ + {'name': 'nature_parks', 'localized_name': 'Nature & Parks'}, + {'name': 'attractions', 'localized_name': 'Attractions'} + ], + 'groups': [ + { + 'name': 'Nature & Parks', + 'localized_name': 'Nature & Parks', + 'categories': [{'name': 'Islands', + 'localized_name': 'Islands'}] + } + ], + 'neighborhood_info': [], + 'trip_types': [ + {'name': 'business', 'localized_name': + 'Business', 'value': '2'}, + {'name': 'couples', 'localized_name': + 'Couples', 'value': '10'}, + {'name': 'solo', 'localized_name': + 'Solo travel', 'value': '11'}, + {'name': 'family', 'localized_name': + 'Family', 'value': '2'}, + {'name': 'friends', 'localized_name': + 'Friends getaway', 'value': '22'} + ], + 'awards': [] + } """ url = ( f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/" From 821b96154fececd953acd8a5d3ade1df2958c6cb Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:50:36 +0800 Subject: [PATCH 06/24] Update tripadvisor.py --- src/agentscope/service/web/tripadvisor.py | 182 ++++++++++++++++++---- 1 file changed, 153 insertions(+), 29 deletions(-) diff --git a/src/agentscope/service/web/tripadvisor.py b/src/agentscope/service/web/tripadvisor.py index c58400350..b07cdaf4c 100644 --- a/src/agentscope/service/web/tripadvisor.py +++ b/src/agentscope/service/web/tripadvisor.py @@ -7,10 +7,10 @@ from agentscope.service.service_status import ServiceExecStatus -# pylint: disable=line-too-long def get_tripadvisor_location_photos( api_key: str, - location_id: str, + location_id: str = None, + query: str = None, language: str = "en", ) -> ServiceResponse: """ @@ -19,8 +19,12 @@ def get_tripadvisor_location_photos( Args: api_key (`str`): Your TripAdvisor API key. - location_id (`str`): + location_id (`str`, optional): The ID of the location for which to retrieve photos. + Required if query is not provided. + query (`str`, optional): + The search query to find a location. Required if + location_id is not provided. language (`str`, optional): The language for the response. Defaults to 'en'. @@ -41,14 +45,23 @@ def get_tripadvisor_location_photos( 'caption': str, 'published_date': str, 'images': { - 'thumbnail': {'height': int, - 'width': int, 'url': str}, + 'thumbnail': { + 'height': int, + 'width': int, + 'url': str + }, 'small': {'height': int, 'width': int, 'url': str}, - 'medium': {'height': int, - 'width': int, 'url': str}, + 'medium': { + 'height': int, + 'width': int, + 'url': str + }, 'large': {'height': int, 'width': int, 'url': str}, - 'original': {'height': int, - 'width': int, 'url': str} + 'original': { + 'height': int, + 'width': int, + 'url': str + } }, 'album': str, 'source': {'name': str, 'localized_name': str}, @@ -61,10 +74,24 @@ def get_tripadvisor_location_photos( Each item in the 'data' list represents a photo associated with the location. + Note: + Either `location_id` or `query` must be provided. If both are provided, + `location_id` takes precedence. + Example: .. code-block:: python - result = get_tripadvisor_location_photos("your_api_key", "123456", "en") # noqa: E501 + # Using location_id + result = get_tripadvisor_location_photos( + "your_api_key", location_id="123456", language="en" + ) + if result.status == ServiceExecStatus.SUCCESS: + print(result.content) + + # Or using a query + result = get_tripadvisor_location_photos( + "your_api_key", query="Eiffel Tower", language="en" + ) if result.status == ServiceExecStatus.SUCCESS: print(result.content) @@ -79,27 +106,60 @@ def get_tripadvisor_location_photos( 'published_date': '2016-09-04T20:40:14.284Z', 'images': { 'thumbnail': {'height': 50, 'width': 50, - 'url': 'https://media-cdn.tripadvisor.com/media/photo-t/0c/d5/8c/26/photo0jpg.jpg'}, # noqa: E501 + 'url': 'https://media-cdn.../photo0.jpg'}, 'small': {'height': 150, 'width': 150, - 'url': 'https://media-cdn.tripadvisor.com/media/photo-l/0c/d5/8c/26/photo0jpg.jpg'}, # noqa: E501 + 'url': 'https://media-cdn.../photo0.jpg'}, 'medium': {'height': 188, 'width': 250, - 'url': 'https://media-cdn.tripadvisor.com/media/photo-f/0c/d5/8c/26/photo0jpg.jpg'}, # noqa: E501 + 'url': 'https://media-cdn.../photo0.jpg'}, 'large': {'height': 413, 'width': 550, - 'url': 'https://media-cdn.tripadvisor.com/media/photo-s/0c/d5/8c/26/photo0jpg.jpg'}, # noqa: E501 + 'url': 'https://media-cdn.../photo0.jpg'}, 'original': {'height': 1920, 'width': 2560, - 'url': 'https://media-cdn.tripadvisor.com/media/photo-c/2560x500/0c/d5/8c/26/photo0jpg.jpg'} # noqa: E501 + 'url': 'https://media-cdn.../photo0.jpg'} }, 'album': 'Other', - 'source': {'name': 'Traveler', - 'localized_name': 'Traveler'}, + 'source': { + 'name': 'Traveler', + 'localized_name': 'Traveler' + }, 'user': {'username': 'EvaFalleth'} }, # ... more photo entries ... ] } } + + Raises: + ValueError: If neither location_id nor query is provided. """ + if location_id is None and query is None: + raise ValueError("Either location_id or query must be provided.") + + if location_id is None: + # Use search_tripadvisor to get the location_id + search_result = search_tripadvisor(api_key, query, language) + if search_result.status != ServiceExecStatus.SUCCESS: + return search_result + + # Get the first location_id from the search results + locations = search_result.content.get("data", []) + if not locations: + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": "No locations found for the given query."}, + ) + + location_id = locations[0]["location_id"] + logger.info(f"Using location_id {location_id} from search results.") + # Warning message if there are multiple locations + if len(locations) > 1: + logger.warning( + f"Multiple locations found for query '{query}'. " + f"Using the first result. " + f"Other {len(locations) - 1} results are ignored.", + ) + + # Now proceed with the original function logic using the location_id url = ( f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/" f"photos?language={language}&key={api_key}" @@ -265,18 +325,24 @@ def search_tripadvisor( def get_tripadvisor_location_details( api_key: str, - location_id: str, + location_id: str = None, + query: str = None, language: str = "en", currency: str = "USD", ) -> ServiceResponse: """ - Get detailed information about a specific location using the TripAdvisor API. + Get detailed information about a specific location + using the TripAdvisor API. Args: api_key (`str`): Your TripAdvisor API key. - location_id (`str`): - The unique identifier for the location. + location_id (`str`, optional): + The unique identifier for the location. Required if + query is not provided. + query (`str`, optional): + The search query to find a location. Required if + location_id is not provided. language (`str`, optional): The language for the response. Defaults to 'en'. currency (`str`, optional): @@ -288,13 +354,34 @@ def get_tripadvisor_location_details( and `content` is the JSON response from TripAdvisor API or error information, which depends on the `status` variable. - If successful, the `content` will be a dictionary with detailed information - about the location, including name, address, ratings, reviews, and more. + If successful, the `content` will be a dictionary with + detailed information about the location, including + name, address, ratings, reviews, and more. + + Note: + Either `location_id` or `query` must be provided. If both are provided, + `location_id` takes precedence. Example: .. code-block:: python - result = get_tripadvisor_location_details("your_api_key", "574818", "en", "USD") + # Using location_id + result = get_tripadvisor_location_details( + "your_api_key", + location_id="574818", + language="en", + currency="USD" + ) + if result.status == ServiceExecStatus.SUCCESS: + print(result.content) + + # Or using a query + result = get_tripadvisor_location_details( + "your_api_key", + query="Socotra Island", + language="en", + currency="USD" + ) if result.status == ServiceExecStatus.SUCCESS: print(result.content) @@ -302,7 +389,7 @@ def get_tripadvisor_location_details( { 'location_id': '574818', 'name': 'Socotra Island', - 'web_url': 'https://www.tripadvisor.com/Attraction_Review-g298087-d574818-Reviews-Socotra_Island-Aden.html?m=66827', + 'web_url': 'https://www.tripadvisor.com/Attraction_Review...', 'address_obj': { 'street2': '', 'city': 'Aden', @@ -317,7 +404,7 @@ def get_tripadvisor_location_details( 'latitude': '12.46342', 'longitude': '53.82374', 'timezone': 'Asia/Aden', - 'write_review': 'https://www.tripadvisor.com/UserReview-g298087-d574818-Socotra_Island-Aden.html?m=66827', + 'write_review': 'https://www.tripadvisor.com/UserReview...', 'ranking_data': { 'geo_location_id': '298087', 'ranking_string': '#1 of 7 things to do in Aden', @@ -326,11 +413,17 @@ def get_tripadvisor_location_details( 'ranking': '1' }, 'rating': '5.0', - 'rating_image_url': 'https://www.tripadvisor.com/img/cdsi/img2/ratings/traveler/5.0-66827-5.svg', + 'rating_image_url': 'https://www.tripadvisor.com/.../5.svg', 'num_reviews': '62', - 'review_rating_count': {'1': '1', '2': '0', '3': '1', '4': '1', '5': '59'}, + 'review_rating_count': { + '1': '1', + '2': '0', + '3': '1', + '4': '1', + '5': '59', + }, 'photo_count': '342', - 'see_all_photos': 'https://www.tripadvisor.com/Attraction_Review-g298087-d574818-m66827-Reviews-Socotra_Island-Aden.html#photos', # noqa: E501 + 'see_all_photos': 'https://www.tripadvisor.com/Attraction...', 'category': {'name': 'attraction', 'localized_name': 'Attraction'}, 'subcategory': [ {'name': 'nature_parks', 'localized_name': 'Nature & Parks'}, @@ -359,7 +452,38 @@ def get_tripadvisor_location_details( ], 'awards': [] } + + Raises: + ValueError: If neither location_id nor query is provided. """ + if location_id is None and query is None: + raise ValueError("Either location_id or query must be provided.") + + if location_id is None: + # Use search_tripadvisor to get the location_id + search_result = search_tripadvisor(api_key, query, language) + if search_result.status != ServiceExecStatus.SUCCESS: + return search_result + + # Get the first location_id from the search results + locations = search_result.content.get("data", []) + if not locations: + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": "No locations found for the given query."}, + ) + + location_id = locations[0]["location_id"] + logger.info(f"Using location_id {location_id} from search results.") + + # Warning message if there are multiple locations + if len(locations) > 1: + logger.warning( + f"Multiple locations found for query '{query}'. " + f"Using the first result. " + f"Other {len(locations) - 1} results are ignored.", + ) + url = ( f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/" f"details?language={language}¤cy={currency}&key={api_key}" From 961135484019b347b068e21ee61b77fa1414b350 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Sun, 4 Aug 2024 21:45:34 +0800 Subject: [PATCH 07/24] update neurosymbolic.py --- .../geoguesser_neurosymbolic.py | 596 ++++++++++++------ examples/game_geoguessr/geoguessr.py | 85 +-- 2 files changed, 448 insertions(+), 233 deletions(-) diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py index 5d9f66c9b..b45882d28 100644 --- a/examples/game_geoguessr/geoguesser_neurosymbolic.py +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -1,19 +1,25 @@ -from IPython.display import Image, display -from IPython.display import clear_output, display +# -*- coding: utf-8 -*- +from IPython.display import display import requests from io import BytesIO from PIL import Image as PILImage import logging -import requests from typing import Optional -from agentscope.service.service_response import ServiceResponse, ServiceExecStatus +from agentscope.service.service_response import ( + ServiceResponse, + ServiceExecStatus, +) from agentscope.service.service_toolkit import ServiceToolkit import agentscope from agentscope.message import Msg from agentscope.agents.dialog_agent import DialogAgent as BaseDialogAgent from agentscope.agents import DialogAgent -import json +from agentscope.service import ( + get_tripadvisor_location_photos, + search_tripadvisor, + get_tripadvisor_location_details, +) import ast from typing import Dict, Any, List @@ -22,25 +28,31 @@ import re import os + class LocationMatcherParser(ParserBase): - """A parser to match locations in natural language text against a predefined list of locations.""" + """ + A parser to match locations in natural language text + against a predefined list of locations. + """ def __init__(self, locations): """ Initialize the parser with a list of location dictionaries. - + Args: - locations (list): A list of dictionaries, each containing location information. + locations (list): A list of dictionaries, + each containing location information. """ self.locations = locations def parse(self, response: ModelResponse) -> ModelResponse: """ Parse the response text to find matches with the predefined locations. - + Args: - response (ModelResponse): The response object containing the text to parse. - + response (ModelResponse): The response object containing + the text to parse. + Returns: ModelResponse: The response object with the parsed result added. """ @@ -48,44 +60,65 @@ def parse(self, response: ModelResponse) -> ModelResponse: matches = [] for location in self.locations: - if re.search(r'\b' + re.escape(location['name']) + r'\b', text, re.IGNORECASE): + if re.search( + r"\b" + re.escape(location["name"]) + r"\b", + text, + re.IGNORECASE, + ): matches.append(location) response.parsed = matches return response - -import agentscope + + agentscope.init( # ... project="xxx", name="xxx", - studio_url="http://127.0.0.1:5000" # The URL of AgentScope Studio + studio_url="http://127.0.0.1:5000", # The URL of AgentScope Studio ) # Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -from agentscope.service import ( - get_tripadvisor_location_photos, - search_tripadvisor, - get_tripadvisor_location_details -) # Initialize the ServiceToolkit and register the TripAdvisor API functions service_toolkit = ServiceToolkit() -service_toolkit.add(search_tripadvisor, api_key="") # Replace with your actual TripAdvisor API key -service_toolkit.add(get_tripadvisor_location_details, api_key="") # Replace with your actual TripAdvisor API key -service_toolkit.add(get_tripadvisor_location_photos, api_key="") # Replace with your actual TripAdvisor API key +service_toolkit.add( + search_tripadvisor, + api_key="", +) # Replace with your actual TripAdvisor API key +service_toolkit.add( + get_tripadvisor_location_details, + api_key="", +) # Replace with your actual TripAdvisor API key +service_toolkit.add( + get_tripadvisor_location_photos, + api_key="", +) # Replace with your actual TripAdvisor API key class ExtendedDialogAgent(BaseDialogAgent): - def __init__(self, name: str, sys_prompt: str, model_config_name: str, use_memory: bool = True, memory_config: Optional[dict] = None): - super().__init__(name, sys_prompt, model_config_name, use_memory, memory_config) + def __init__( + self, + name: str, + sys_prompt: str, + model_config_name: str, + use_memory: bool = True, + memory_config: Optional[dict] = None, + ): + super().__init__( + name, + sys_prompt, + model_config_name, + use_memory, + memory_config, + ) self.service_toolkit = service_toolkit self.current_location = None self.current_details = None self.game_state = "start" - + def parse_service_response(self, response: str) -> dict: """ Parse the service response string and extract the JSON content. @@ -95,46 +128,95 @@ def parse_service_response(self, response: str) -> dict: dict: The parsed JSON content. """ result = {} - lines = response.split('\n') + lines = response.split("\n") for line in lines: - if '[STATUS]:' in line: - status = line.split('[STATUS]:')[-1].strip() - result['status'] = ServiceExecStatus.SUCCESS if status == 'SUCCESS' else ServiceExecStatus.ERROR - if '[RESULT]:' in line: - json_str = line.split('[RESULT]:')[-1].strip() - result['content'] = ast.literal_eval(json_str) + if "[STATUS]:" in line: + status = line.split("[STATUS]:")[-1].strip() + result["status"] = ( + ServiceExecStatus.SUCCESS + if status == "SUCCESS" + else ServiceExecStatus.ERROR + ) + if "[RESULT]:" in line: + json_str = line.split("[RESULT]:")[-1].strip() + result["content"] = ast.literal_eval(json_str) return result def propose_location(self): messages = [ - {"role": "system", "content": "You are a game master for a geography guessing game. Propose an interesting location as for the player to guess. Diversify your proposal and give less known ones."}, - {"role": "user", "content": "Propose an interesting location for the player to guess. Only output your final proposed location."} + { + "role": "system", + "content": ( + "You are a game master for a geography guessing game. " + "Propose an interesting location for the player to guess. " + "Diversify your proposal and give less known ones." + ), + }, + { + "role": "user", + "content": ( + "Propose an interesting location for the player to guess. " + "Only output your final proposed location." + ), + }, ] response = self.model(messages).text.strip() return response def search_and_select_location(self, proposed_location): response_str = self.service_toolkit.parse_and_call_func( - [{"name": "search_tripadvisor", "arguments": {"query": proposed_location}}] + [ + { + "name": "search_tripadvisor", + "arguments": {"query": proposed_location}, + }, + ], ) result = self.parse_service_response(response_str) - if result['status'] == ServiceExecStatus.SUCCESS and result['content']['data']: - locations = result['content']['data'] + if ( + result["status"] == ServiceExecStatus.SUCCESS + and result["content"]["data"] + ): + locations = result["content"]["data"] if len(locations) > 1: messages = [ - {"role": "system", "content": "You are selecting the most appropriate location from a list of search results."}, - {"role": "user", "content": f"Select the location that best matches '{proposed_location}' from this list:\n" + - "\n".join([f"{i+1}. {loc['name']}, {loc.get('address_obj', {}).get('city', 'Unknown City')}, {loc.get('address_obj', {}).get('country', 'Unknown Country')}" for i, loc in enumerate(locations)]) + - "\nRespond with only the number of your selection."} + { + "role": "system", + "content": ( + "You are selecting the most appropriate location" + " from a list of search results." + ), + }, + { + "role": "user", + "content": ( + f"Select the location that best matches" + f" '{proposed_location}' from this list:\n" + + "\n".join( + [ + f"{i+1}. {loc['name']}, " + f"{loc.get('address_obj', {}).get('city', 'Unknown City')}, " + f"{loc.get('address_obj',{}).get('country','Unknown Country')}" + for i, loc in enumerate(locations) + ], + ) + + "\nRespond with only the number" + " of your selection." + ), + }, ] selection_response = self.model(messages).text.strip() try: # Try to extract the number from the response - selection = int(''.join(filter(str.isdigit, selection_response))) - 1 + selection = ( + int("".join(filter(str.isdigit, selection_response))) + - 1 + ) if 0 <= selection < len(locations): return locations[selection] else: - # If the extracted number is out of range, return the first location + # If the extracted number is out of range, + # return the first location return locations[0] except ValueError: # If no number can be extracted, return the first location @@ -143,37 +225,53 @@ def search_and_select_location(self, proposed_location): return locations[0] return None - def get_location_details(self, location_id): response_str = self.service_toolkit.parse_and_call_func( - [{"name": "get_tripadvisor_location_details", "arguments": {"location_id": location_id}}] + [ + { + "name": "get_tripadvisor_location_details", + "arguments": {"location_id": location_id}, + }, + ], ) return self.parse_service_response(response_str) - + def get_location_photos(self, location_id: str) -> dict: """ Get the largest photo for a specific location. - + Args: location_id (str): The location ID to get photos for. - + Returns: dict: The largest photo information including the URL. """ - logger.info(f"Calling TripAdvisor API for location photos: {location_id}") + logger.info( + f"Calling TripAdvisor API for location photos: {location_id}", + ) response_str = self.service_toolkit.parse_and_call_func( - [{"name": "get_tripadvisor_location_photos", "arguments": {"location_id": location_id}}] + [ + { + "name": "get_tripadvisor_location_photos", + "arguments": {"location_id": location_id}, + }, + ], ) logger.info(f"TripAdvisor location photos result: {response_str}") result = self.parse_service_response(response_str) - return result['content']['largest_photo'] if result['status'] == ServiceExecStatus.SUCCESS else None - + largest_photo = self.find_largest_photo(result["content"]["data"]) + return ( + largest_photo + if result["status"] == ServiceExecStatus.SUCCESS + else None + ) def display_photo(self, location_id): - photos = self.get_location_photos(location_id) - if photos: + largest_photo = self.get_location_photos(location_id) + + if largest_photo: try: - image_url = photos['url'] + image_url = largest_photo["url"] response = requests.get(image_url) img = PILImage.open(BytesIO(response.content)) display(img) @@ -182,64 +280,87 @@ def display_photo(self, location_id): logger.error(f"Error displaying image: {str(e)}") return False - # def check_guess(self, user_guess, ancestors): - # for ancestor in ancestors: - # if user_guess.lower() in ancestor['name'].lower(): - # return ancestor['level'], ancestor['name'] - # return None, None - def check_guess_location(self, user_guess, location): - messages = [ - {"role": "system", "content": '''You are the gamemaster of a geoguessr game. Check if the player's guess: '''+ str(user_guess)+''' means the same place/location as '''+ str(location)+'''. If yes, return 'True'. Else, return 'False'. Only output one of the two options given and nothing else.'''}, - {"role": "user", "content": user_guess} + { + "role": "system", + "content": ( + "You are the gamemaster of a geoguessr game." + " Check if the player's guess: " + + str(user_guess) + + " means the same place/location as " + + str(location) + + ". If yes, return 'True'. Else, return 'False'." + " Only output one of the two " + "options given and nothing else." + ), + }, + {"role": "user", "content": user_guess}, ] response = self.model(messages).text.strip() logger.info(f"check_guess: {response}") return response - - def check_guess_ancestors(self, user_guess, ancestors): - # parser = LocationMatcherParser(ancestors) - # response = ModelResponse(text="This location is an indigenous village nestled within a picturesque canyon in the southwestern part of the United States. It's accessible via a trail that winds through the rugged landscape of Arizona.") - - # parsed_response = parser.parse(response) - matches = [] for location in ancestors: - if re.search(r'\b' + re.escape(location['name']) + r'\b', user_guess, re.IGNORECASE): + if re.search( + r"\b" + re.escape(location["name"]) + r"\b", + user_guess, + re.IGNORECASE, + ): matches.append(location) if not matches: - return 'None','None','False' + return "None", "None", "False" else: messages = [ - {"role": "system", "content": '''Check if '''+ str(matches)+''' include the smallest level in '''+ str(ancestors)+''' based on their respective levels. If yes, return the smallest matched name and the corresponding level as 'level,name,True'. Else, return 'False'. Note that the placeholders 'level', 'name' in the output are to be replaced by the actual values. Only output one of the two options given and nothing else.'''}, - {"role": "user", "content": user_guess} + { + "role": "system", + "content": ( + "Check if " + + str(matches) + + " include the smallest level in " + + str(ancestors) + + " based on their respective levels. If yes," + " return the smallest matched name and the " + "corresponding level as 'level,name,True'. " + "Else, return 'False'. Note that the placeholders " + "'level', 'name' in the output are to be replaced" + " by the actual values. Only output one " + "of the two options given and nothing else." + ), + }, + {"role": "user", "content": user_guess}, ] - # messages = [ - # {"role": "system", "content": '''You're a game master for a geography guessing game. Check user's guess if any part of the guess (including Country) matches any name at any level in ancestors: '''+ str(ancestors)+'''. If doesn't match at all levels, return 'None,None,False'. - # If it matches but the smallest matched name's corresponding level is not the smallest level available in ancestors, return the smallest **matched** name and the corresponding level as 'level,name,False'. - # If it matches and the smallest matched name's corresponding level is the smallest level available in ancestors, return the smallest matched name and the corresponding level as 'level,name,True'. - # Only output one of the three options given and nothing else.'''}, - # {"role": "user", "content": user_guess} - # ] + response = self.model(messages).text.strip() - if 'True' in response: + if "True" in response: logger.info(f"check_guess: {response}") - response = response.split(',') - return response[0],response[1],response[2] + response = response.split(",") + return response[0], response[1], response[2] else: messages = [ - {"role": "system", "content": '''Return the smallest name based on their respective 'level' values and the corresponding 'level' values in '''+ str(matches)+''' as 'level,name,False'. Note that the placeholders 'level', 'name' in the output are to be replaced by the actual values. Only output in the given format and nothing else.'''}, - {"role": "user", "content": user_guess} + { + "role": "system", + "content": ( + "Return the smallest name based on their" + " respective 'level' values and the " + "corresponding 'level' values in " + + str(matches) + + " as 'level,name,False'. Note that the " + "placeholders 'level', 'name' in the output are" + " to be replaced by the actual values. " + "Only output in the given format and nothing else." + ), + }, + {"role": "user", "content": user_guess}, ] response = self.model(messages).text.strip() logger.info(f"check_guess: {response}") - response = response.split(',') - return response[0],response[1],response[2] + response = response.split(",") + return response[0], response[1], response[2] def save_image_from_url(self, url, save_path): try: @@ -250,12 +371,12 @@ def save_image_from_url(self, url, save_path): # Get the file name from the URL file_name = os.path.basename(url) - file_name = 'image'+os.path.splitext(file_name)[1] + file_name = "image" + os.path.splitext(file_name)[1] # Full path to save the image full_path = os.path.join(save_path, file_name) # Open a local file with write-binary mode - with open(full_path, 'wb') as file: + with open(full_path, "wb") as file: for chunk in response.iter_content(chunk_size=8192): file.write(chunk) @@ -264,66 +385,176 @@ def save_image_from_url(self, url, save_path): except Exception as e: print(f"An error occurred: {e}") + def find_largest_photo( + self, + photos: List[Dict[str, Any]], + ) -> Dict[str, Any]: + """Find the photo with the largest + dimensions from the list of photos.""" + largest_photo_info = {} + max_area = 0 + + for item in photos: + for image_info in item["images"].values(): + height = image_info["height"] + width = image_info["width"] + area = height * width + + if area > max_area: + max_area = area + largest_photo_info = { + "url": image_info["url"], + "height": height, + "width": width, + "caption": item.get("caption", ""), + "album": item.get("album", ""), + "published_date": item.get("published_date", ""), + "id": item.get("id", ""), + "source": item.get("source", {}), + "user": item.get("user", {}), + } + + return largest_photo_info + def get_hint(self, details): messages = [ - {"role": "system", "content": "You're a game master for a geography guessing game."}, - {"role": "user", "content": f"give the user some hint about the location based on the info from {details}. Don't make it too obvious."} + { + "role": "system", + "content": ( + "You're a game master for a geography guessing game." + ), + }, + { + "role": "user", + "content": ( + f"give the user some hint about the location" + f" based on the info from {details}. " + "Don't make it too obvious." + ), + }, ] response = self.model(messages).text.strip() logger.info(f"Hint: {response}") return response - def handle_input(self, user_input: dict) -> dict: - query = user_input['text'] + query = user_input["text"] if self.game_state == "start": photo_displayed = False while not photo_displayed: proposed_location = self.propose_location() - self.current_location = self.search_and_select_location(proposed_location) - print('self.current_location: ', self.current_location) + self.current_location = self.search_and_select_location( + proposed_location, + ) + print("self.current_location: ", self.current_location) if not self.current_location: - return {"text": "I'm sorry, I couldn't find a suitable location. Let's try again."} - - self.current_details = self.get_location_details(self.current_location['location_id']) - if self.current_details['status'] != ServiceExecStatus.SUCCESS: - return {"text": "I'm having trouble getting details for this location. Let's try again."} - ancestors = self.current_details['content'].get('ancestors', []) - print('ancestors: ', ancestors) - - # clear_output(wait=True) - display(None) - photo_displayed = self.display_photo(self.current_location['location_id']) - photos = self.get_location_photos(self.current_location['location_id']) - - response = "Let's play a geography guessing game! I've chosen a location and displayed an image of it. " - # response += f"![image]({photos['url']})" - response += " Can you guess which country, state, region, city, or municipality this location is in?" + return { + "text": ( + "I'm sorry, I couldn't find a suitable location. " + "Let's try again." + ), + } + + self.current_details = self.get_location_details( + self.current_location["location_id"], + ) + if self.current_details["status"] != ServiceExecStatus.SUCCESS: + return { + "text": ( + "I'm having trouble getting details" + " for this location. " + "Let's try again." + ), + } + ancestors = self.current_details["content"].get( + "ancestors", + [], + ) + print("ancestors: ", ancestors) + + photo_displayed = self.display_photo( + self.current_location["location_id"], + ) + photos = self.get_location_photos( + self.current_location["location_id"], + ) + + response = ( + "Let's play a geography guessing game!" + " I've chosen a location and displayed " + "an image of it. Can you guess which" + " country, state, region, city, or " + "municipality this location is in?" + ) self.game_state = "guessing" - image_path = self.save_image_from_url(photos['url'], save_path = "./images") - - return [{"text": response}, {"image": image_path}] + image_path = self.save_image_from_url( + photos["url"], + save_path="./images", + ) + + return [ + {"text": response}, + {"image": image_path}, + {"image_for_display": photos["url"]}, + ] elif self.game_state == "guessing": - if self.check_guess_location(query.lower(), self.current_location['name'].lower()) == 'True': + if ( + self.check_guess_location( + query.lower(), + self.current_location["name"].lower(), + ) + == "True" + ): self.game_state = "end" - return {"text": f"Congratulations! You've guessed correctly. The location is indeed in {self.current_location['name']}."} - ancestors = self.current_details['content'].get('ancestors', []) - level, correct_name, is_smallest = self.check_guess_ancestors(query, ancestors) - - if level != 'None': - if is_smallest == 'True': + return { + "text": ( + f"Congratulations! You've guessed correctly." + " The location is indeed in " + f"{self.current_location['name']}." + ), + } + ancestors = self.current_details["content"].get("ancestors", []) + level, correct_name, is_smallest = self.check_guess_ancestors( + query, + ancestors, + ) + + if level != "None": + if is_smallest == "True": self.game_state = "end" - return {"text": f"Congratulations! You've guessed correctly. The location is indeed in {level}: {correct_name}."} + return { + "text": ( + f"Congratulations! You've guessed correctly." + f" The location is indeed in {level}: " + f"{correct_name}." + ), + } else: - return {"text": f"Good guess! {level}: {correct_name} is correct, but can you be more specific? Try guessing a smaller region or city."} + return { + "text": ( + f"Good guess! {level}: {correct_name} is correct," + " but can you be more specific? " + "Try guessing a smaller region or city." + ), + } else: - hint = self.get_hint(self.current_details['content']) - return {"text": f"I'm sorry, that's not correct. Here's a hint: {hint} Try guessing again!"} + hint = self.get_hint(self.current_details["content"]) + return { + "text": ( + f"I'm sorry, that's not correct. Here's a hint: {hint}" + " Try guessing again!" + ), + } else: - return {"text": "I'm sorry, I don't understand. Let's start a new game!"} + return { + "text": ( + "I'm sorry, I don't understand. Let's start a new game!" + ), + } + # Initialize AgentScope and run def main() -> None: @@ -346,8 +577,8 @@ def main() -> None: "model_name": "qwen-max-1201", "api_key": "", "generate_args": { - "temperature": 0.1 - } + "temperature": 0.1, + }, }, { "config_name": "dashscope_multimodal-qwen-vl-max", @@ -355,8 +586,8 @@ def main() -> None: "model_name": "qwen-vl-max", "api_key": "", "generate_args": { - "temperature": 0.01 - } + "temperature": 0.01, + }, }, { "config_name": "gpt-4o_config", @@ -365,8 +596,8 @@ def main() -> None: "api_key": "", "generate_args": { "temperature": 0.8, + }, }, - } ], ) @@ -380,78 +611,51 @@ def main() -> None: player_agent = DialogAgent( name="player", - sys_prompt='''You're a player in a geoguessr-like turn-based game. Upon getting an image from the gamemaster, you are supposed to guess where is the place shown in the image. Your guess can be a country, - a state, a region, a city, etc., but try to be as precise as possoble. If your answer is not correct, try again based on the hint given by the gamemaster.''', - model_config_name="dashscope_multimodal-qwen-vl-max", # replace by your model config name + sys_prompt="""You're a player in a geoguessr-like turn-based game. + Upon getting an image from the gamemaster, you are supposed to guess + where is the place shown in the image. Your guess can be a country, + a state, a region, a city, etc., but try to be as precise as possoble. + If your answer is not correct, try again based on the hint given + by the gamemaster.""", + model_config_name=( + "dashscope_multimodal-qwen-vl-max" + ), # replace by your model config name ) # Start the game x = None - while gamemaster_agent.game_state != 'guessing': - - response = gamemaster_agent.handle_input({"text": "Let's start the game"}) - x = Msg("GeoGuessr Gamemaster", response[0]['text'], url=response[1]['image']) - - # x = Msg("GeoGuessr Gamemaster", response) + while gamemaster_agent.game_state != "guessing": + response = gamemaster_agent.handle_input( + {"text": "Let's start the game"}, + ) + x = Msg( + "GeoGuessr Gamemaster", + response[0]["text"], + ) + gamemaster_agent.speak(x) - print("Game Master:", response[0]['text']) + x["url"] = response[1]["image"] + + y = Msg( + "GeoGuessr Gamemaster", + "image", + url=response[2]["image_for_display"], + ) + gamemaster_agent.speak(y) + print("Game Master:", response[0]["text"]) # Main game loop - - while gamemaster_agent.game_state != 'end': - # Clear previous output - # clear_output(wait=True) - - # Display the image and game master's text - # if gamemaster_agent.game_state == "guessing": - # gamemaster_agent.display_photo(gamemaster_agent.current_location['location_id']) - # print("Game Master:", response['text']) - - # Force display update - display(None) - - # user_input = input("Your guess: ") + + while gamemaster_agent.game_state != "end": x = player_agent(x) - if 'quit' in x['content'].lower(): + if "quit" in x["content"].lower(): print("Thanks for playing!") break - response = gamemaster_agent.handle_input({"text": x['content']}) - x = Msg("GeoGuessr Gamemaster", response['text']) + response = gamemaster_agent.handle_input({"text": x["content"]}) + x = Msg("GeoGuessr Gamemaster", response["text"]) gamemaster_agent.speak(x) - print("Game Master:", response['text']) - - - #pve - - # # Start the game - # x = None - # while gamemaster_agent.game_state != 'guessing': - - # response = gamemaster_agent.handle_input({"text": "Let's start the game"}) - # x = Msg("GeoGuessr Gamemaster", response['text']) - - # print("Game Master:", response['text']) - - # # Main game loop - - # while gamemaster_agent.game_state != 'end': - # # Clear previous output - # # clear_output(wait=True) - - # # Display the image and game master's text - # # if gamemaster_agent.game_state == "guessing": - # # gamemaster_agent.display_photo(gamemaster_agent.current_location['location_id']) - # print("Game Master:", response['text']) - - # # Force display update - # display(None) - - # user_input = input("Your guess: ") - # if user_input.lower() == 'quit': - # print("Thanks for playing!") - # break - # response = gamemaster_agent.handle_input({"text": user_input}) - # print("Game Master:", response['text']) + print("Game Master:", response["text"]) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/game_geoguessr/geoguessr.py b/examples/game_geoguessr/geoguessr.py index 8a794d790..5ed181a4e 100644 --- a/examples/game_geoguessr/geoguessr.py +++ b/examples/game_geoguessr/geoguessr.py @@ -1,13 +1,15 @@ +# -*- coding: utf-8 -*- import requests from typing import Dict, Any, List from agentscope.service import ServiceResponse, ServiceExecStatus from agentscope.agents import DialogAgent import agentscope + agentscope.init( # ... project="xxx", name="xxx", - studio_url="http://127.0.0.1:5000" # The URL of AgentScope Studio + studio_url="http://127.0.0.1:5000", # The URL of AgentScope Studio ) YOUR_MODEL_CONFIGURATION_NAME = "dashscope_chat-qwen-max" @@ -22,30 +24,31 @@ # }, # } -YOUR_MODEL_CONFIGURATION = [{ - "config_name": "dashscope_chat-qwen-max", - "model_type": "dashscope_chat", - "model_name": "qwen-max", - "api_key": "", - "generate_args": { - "temperature": 0.1 - } -}, - -{ - "config_name": "dashscope_multimodal-qwen-vl-max", - "model_type": "dashscope_multimodal", - "model_name": "qwen-vl-max", - "api_key": "", - "generate_args": { - "temperature": 0.2 - } -},] +YOUR_MODEL_CONFIGURATION = [ + { + "config_name": "dashscope_chat-qwen-max", + "model_type": "dashscope_chat", + "model_name": "qwen-max", + "api_key": "", + "generate_args": { + "temperature": 0.1, + }, + }, + { + "config_name": "dashscope_multimodal-qwen-vl-max", + "model_type": "dashscope_multimodal", + "model_name": "qwen-vl-max", + "api_key": "", + "generate_args": { + "temperature": 0.2, + }, + }, +] from agentscope.service import ( get_tripadvisor_location_photos, search_tripadvisor, - get_tripadvisor_location_details + get_tripadvisor_location_details, ) # def get_tripadvisor_location_photos(api_key: str, location_id: str, language: str = 'en') -> ServiceResponse: @@ -123,7 +126,6 @@ # ) - # # Define the TripAdvisor API query function # def search_tripadvisor(api_key: str, query: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: # """ @@ -199,15 +201,25 @@ # status=ServiceExecStatus.ERROR, # content={"error": str(e)} # ) - + from agentscope.service import ServiceToolkit # Initialize the ServiceToolkit and register the TripAdvisor API functions service_toolkit = ServiceToolkit() -service_toolkit.add(search_tripadvisor, api_key="") # Replace with your actual TripAdvisor API key -service_toolkit.add(get_tripadvisor_location_details, api_key="") # Replace with your actual TripAdvisor API key -service_toolkit.add(get_tripadvisor_location_photos, api_key="") # Replace with your actual TripAdvisor API key +service_toolkit.add( + search_tripadvisor, + api_key="", +) # Replace with your actual TripAdvisor API key +service_toolkit.add( + get_tripadvisor_location_details, + api_key="", +) # Replace with your actual TripAdvisor API key +service_toolkit.add( + get_tripadvisor_location_photos, + api_key="", +) # Replace with your actual TripAdvisor API key import json + print(json.dumps(service_toolkit.json_schemas, indent=4)) from agentscope.agents import ReActAgent @@ -217,24 +229,24 @@ gamemaster_agent = ReActAgent( name="gamemaster", - sys_prompt='''You are the gamemaster of a geoguessr-like game. You interacts with the player following the procedures below: + sys_prompt="""You are the gamemaster of a geoguessr-like game. You interacts with the player following the procedures below: 1. You propose a location (anywhere on earth, the mroe random and low-profile the better, diversify your proposal), then uses the tool `search_tripadvisor` to get its `location_id`; if multiple places are returned, you choose the one that matches what you proposed the most. 2. You use the method `get_tripadvisor_location_photos` to get the url of the image about this location, display the url as part of your output under 'speeak' (use '![image]' as alt text do not use the real name of the lcoation), and asks the player to guess which country/state/region/city/municipality this location is in. If the url is not available for this particular location, repeat step 1 and 2 until a valid image url is found. 3. You use the tool `get_tripadvisor_location_details` to get this location's details. 4. If the player's answer matches exactly the locaiton name correspond to the most precise `level` in `ancestors` in the returned details from step 2, they have won the game and game is over. Note that it is sufficient for the player to just give the exact location name to win the game. Congratulate the player. If the player's answer corresponds to other `level` in `ancestors` instead of the most precise one, encourage the player to give a more precise guess. if the player's answer does not matches the location name, nor any of the values of `name` in `ancestors` in the returned details from step 2, give the player a hint from the details from step 2 that is not too revealing, and ask the player to guess again. Under no circumstances should you reveal the name of the location or of any object in the image before the player guesses it correctly. -When the game is over, append 'exit = True' to your last output.''', +When the game is over, append 'exit = True' to your last output.""", model_config_name="dashscope_chat-qwen-max", - service_toolkit=service_toolkit, - verbose=False, # set verbose to True to show the reasoning process + service_toolkit=service_toolkit, + verbose=False, # set verbose to True to show the reasoning process ) player_agent = DialogAgent( - name="player", - sys_prompt='''You're a player in a geoguessr-like turn-based game. Upon getting the url of an image from the gamemaster, you are supposed to guess where is the place shown in the image. Your guess can be a country, - a state, a region, a city, etc., but try to be as precise as possoble. If your answer is not correct, try again based on the hint given by the gamemaster.''', - model_config_name="dashscope_multimodal-qwen-vl-max", # replace by your model config name - ) + name="player", + sys_prompt="""You're a player in a geoguessr-like turn-based game. Upon getting the url of an image from the gamemaster, you are supposed to guess where is the place shown in the image. Your guess can be a country, + a state, a region, a city, etc., but try to be as precise as possoble. If your answer is not correct, try again based on the hint given by the gamemaster.""", + model_config_name="dashscope_multimodal-qwen-vl-max", # replace by your model config name +) # print("#"*80) # print(agent.sys_prompt) @@ -244,6 +256,7 @@ user = UserAgent(name="User") from agentscope.message import Msg + x = None # x = Msg("Bob", "What about this picture I took?", url="https://media-cdn.tripadvisor.com/media/photo-w/1a/9e/7f/9d/eiffeltoren.jpg") # gamemaster_agent.speak(x) @@ -255,5 +268,3 @@ break x = player_agent(x) # x = user(x) - - From 315c612a9801e9ef81460e5fdad6073160cc360c Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:28:51 +0800 Subject: [PATCH 08/24] updated neurosymbolic --- .../geoguesser_neurosymbolic.py | 271 +++++++------ examples/game_geoguessr/geoguessr.py | 363 ++++++++---------- 2 files changed, 327 insertions(+), 307 deletions(-) diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py index b45882d28..047f88162 100644 --- a/examples/game_geoguessr/geoguesser_neurosymbolic.py +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -1,11 +1,15 @@ # -*- coding: utf-8 -*- +from typing import Dict, Any, List, Optional, Tuple, Union +import os +import re +import ast +import logging +from io import BytesIO + from IPython.display import display import requests -from io import BytesIO from PIL import Image as PILImage -import logging -from typing import Optional from agentscope.service.service_response import ( ServiceResponse, ServiceExecStatus, @@ -20,13 +24,9 @@ search_tripadvisor, get_tripadvisor_location_details, ) -import ast -from typing import Dict, Any, List from agentscope.parsers import ParserBase from agentscope.models import ModelResponse -import re -import os class LocationMatcherParser(ParserBase): @@ -35,7 +35,7 @@ class LocationMatcherParser(ParserBase): against a predefined list of locations. """ - def __init__(self, locations): + def __init__(self, locations: List[Dict[str, Any]]): """ Initialize the parser with a list of location dictionaries. @@ -142,7 +142,17 @@ def parse_service_response(self, response: str) -> dict: result["content"] = ast.literal_eval(json_str) return result - def propose_location(self): + def propose_location(self) -> str: + """ + Propose an interesting location for the player to guess. + + This method uses the model to generate a proposal for an interesting + location that the player will try to guess. It aims to diversify the + proposals and include less known locations. + + Returns: + str: The proposed location as a string. + """ messages = [ { "role": "system", @@ -163,7 +173,23 @@ def propose_location(self): response = self.model(messages).text.strip() return response - def search_and_select_location(self, proposed_location): + def search_and_select_location( + self, + proposed_location: str, + ) -> Optional[Dict[str, Any]]: + """ + Search for a location using TripAdvisor API and select the most appropriate result. + + This method searches for a location using the provided query, then either + automatically selects the first result or asks the model to choose the best + match if multiple results are returned. + + Args: + proposed_location (str): The location to search for. + + Returns: + dict: The selected location information, or None if no location is found. + """ response_str = self.service_toolkit.parse_and_call_func( [ { @@ -225,7 +251,7 @@ def search_and_select_location(self, proposed_location): return locations[0] return None - def get_location_details(self, location_id): + def get_location_details(self, location_id: str) -> Dict[str, Any]: response_str = self.service_toolkit.parse_and_call_func( [ { @@ -236,7 +262,10 @@ def get_location_details(self, location_id): ) return self.parse_service_response(response_str) - def get_location_photos(self, location_id: str) -> dict: + def get_location_photos( + self, + location_id: str, + ) -> Optional[Dict[str, Any]]: """ Get the largest photo for a specific location. @@ -247,8 +276,10 @@ def get_location_photos(self, location_id: str) -> dict: dict: The largest photo information including the URL. """ logger.info( - f"Calling TripAdvisor API for location photos: {location_id}", + "Calling TripAdvisor API for location photos: %s", + location_id, ) + response_str = self.service_toolkit.parse_and_call_func( [ { @@ -257,7 +288,7 @@ def get_location_photos(self, location_id: str) -> dict: }, ], ) - logger.info(f"TripAdvisor location photos result: {response_str}") + logger.info("TripAdvisor location photos result: %s", response_str) result = self.parse_service_response(response_str) largest_photo = self.find_largest_photo(result["content"]["data"]) return ( @@ -266,7 +297,7 @@ def get_location_photos(self, location_id: str) -> dict: else None ) - def display_photo(self, location_id): + def display_photo(self, location_id: str) -> bool: largest_photo = self.get_location_photos(location_id) if largest_photo: @@ -277,10 +308,10 @@ def display_photo(self, location_id): display(img) return True except Exception as e: - logger.error(f"Error displaying image: {str(e)}") + logger.error("Error displaying image: %s", str(e)) return False - def check_guess_location(self, user_guess, location): + def check_guess_location(self, user_guess: str, location: str) -> str: messages = [ { "role": "system", @@ -299,10 +330,14 @@ def check_guess_location(self, user_guess, location): ] response = self.model(messages).text.strip() - logger.info(f"check_guess: {response}") + logger.info("check_guess: %s", response) return response - def check_guess_ancestors(self, user_guess, ancestors): + def check_guess_ancestors( + self, + user_guess: str, + ancestors: List[Dict[str, Any]], + ) -> Tuple[str, str, str]: matches = [] for location in ancestors: @@ -337,7 +372,7 @@ def check_guess_ancestors(self, user_guess, ancestors): response = self.model(messages).text.strip() if "True" in response: - logger.info(f"check_guess: {response}") + logger.info("check_guess: %s", response) response = response.split(",") return response[0], response[1], response[2] else: @@ -358,11 +393,11 @@ def check_guess_ancestors(self, user_guess, ancestors): {"role": "user", "content": user_guess}, ] response = self.model(messages).text.strip() - logger.info(f"check_guess: {response}") + logger.info("check_guess: %s", response) response = response.split(",") return response[0], response[1], response[2] - def save_image_from_url(self, url, save_path): + def save_image_from_url(self, url: str, save_path: str) -> Optional[str]: try: os.makedirs(save_path, exist_ok=True) # Send a HTTP request to the URL @@ -384,6 +419,7 @@ def save_image_from_url(self, url, save_path): return full_path except Exception as e: print(f"An error occurred: {e}") + return None # Return None in case of an error def find_largest_photo( self, @@ -416,7 +452,19 @@ def find_largest_photo( return largest_photo_info - def get_hint(self, details): + def get_hint(self, details: Dict[str, Any]) -> str: + """ + Generate a hint about the location based on the provided details. + + This method uses the model to create a hint that gives the user some + information about the location without making it too obvious. + + Args: + details (Dict[str, Any]): A dictionary containing details about the location. + + Returns: + str: A hint about the location. + """ messages = [ { "role": "system", @@ -434,125 +482,120 @@ def get_hint(self, details): }, ] response = self.model(messages).text.strip() - logger.info(f"Hint: {response}") + logger.info("check_guess: %s", response) return response def handle_input(self, user_input: dict) -> dict: query = user_input["text"] + response = {} if self.game_state == "start": - photo_displayed = False - while not photo_displayed: - proposed_location = self.propose_location() - self.current_location = self.search_and_select_location( - proposed_location, - ) - print("self.current_location: ", self.current_location) - if not self.current_location: - return { - "text": ( - "I'm sorry, I couldn't find a suitable location. " - "Let's try again." - ), - } + response = self.handle_start_state() + elif self.game_state == "guessing": + response = self.handle_guessing_state(query) + else: + response = { + "text": "I'm sorry, I don't understand. Let's start a new game!", + } - self.current_details = self.get_location_details( - self.current_location["location_id"], - ) - if self.current_details["status"] != ServiceExecStatus.SUCCESS: - return { - "text": ( - "I'm having trouble getting details" - " for this location. " - "Let's try again." - ), - } - ancestors = self.current_details["content"].get( - "ancestors", - [], - ) - print("ancestors: ", ancestors) + return response - photo_displayed = self.display_photo( - self.current_location["location_id"], - ) - photos = self.get_location_photos( - self.current_location["location_id"], - ) + def handle_start_state( + self, + ) -> Union[Dict[str, str], List[Dict[str, Union[str, Optional[str]]]]]: + photo_displayed = False + photos: Any = None + while not photo_displayed: + proposed_location = self.propose_location() + self.current_location = self.search_and_select_location( + proposed_location, + ) + print("self.current_location: ", self.current_location) + + if not self.current_location: + return { + "text": "I'm sorry, I couldn't find a suitable location. Let's try again.", + } + + self.current_details = self.get_location_details( + self.current_location["location_id"], + ) + if self.current_details["status"] != ServiceExecStatus.SUCCESS: + return { + "text": "I'm having trouble getting details for this location. Let's try again.", + } + + ancestors = self.current_details["content"].get("ancestors", []) + print("ancestors: ", ancestors) - response = ( - "Let's play a geography guessing game!" - " I've chosen a location and displayed " - "an image of it. Can you guess which" - " country, state, region, city, or " - "municipality this location is in?" + photo_displayed = self.display_photo( + self.current_location["location_id"], ) - self.game_state = "guessing" + photos = self.get_location_photos( + self.current_location["location_id"], + ) + + response = "Let's play a geography guessing game! I've chosen a location and displayed an image of it. Can you guess which country, state, region, city, or municipality this location is in?" + self.game_state = "guessing" + if isinstance(photos, dict) and isinstance(photos.get("url"), str): image_path = self.save_image_from_url( photos["url"], save_path="./images", ) - return [ {"text": response}, {"image": image_path}, {"image_for_display": photos["url"]}, ] + else: + return {"text": response} - elif self.game_state == "guessing": - if ( - self.check_guess_location( - query.lower(), - self.current_location["name"].lower(), - ) - == "True" - ): + def handle_guessing_state(self, query: str) -> Dict[str, str]: + if self.current_location is None: + return { + "text": "I'm sorry, there seems to be an issue with the current location. Let's start a new game.", + } + + if ( + self.check_guess_location( + query.lower(), + self.current_location["name"].lower(), + ) + == "True" + ): + self.game_state = "end" + return { + "text": f"Congratulations! You've guessed correctly. The location is indeed in {self.current_location['name']}.", + } + + if ( + self.current_details is None + or "content" not in self.current_details + ): + return { + "text": "I'm sorry, there seems to be an issue with the location details. Let's start a new game.", + } + + ancestors = self.current_details["content"].get("ancestors", []) + level, correct_name, is_smallest = self.check_guess_ancestors( + query, + ancestors, + ) + + if level != "None": + if is_smallest == "True": self.game_state = "end" return { - "text": ( - f"Congratulations! You've guessed correctly." - " The location is indeed in " - f"{self.current_location['name']}." - ), + "text": f"Congratulations! You've guessed correctly. The location is indeed in {level}: {correct_name}.", } - ancestors = self.current_details["content"].get("ancestors", []) - level, correct_name, is_smallest = self.check_guess_ancestors( - query, - ancestors, - ) - - if level != "None": - if is_smallest == "True": - self.game_state = "end" - return { - "text": ( - f"Congratulations! You've guessed correctly." - f" The location is indeed in {level}: " - f"{correct_name}." - ), - } - else: - return { - "text": ( - f"Good guess! {level}: {correct_name} is correct," - " but can you be more specific? " - "Try guessing a smaller region or city." - ), - } else: - hint = self.get_hint(self.current_details["content"]) return { - "text": ( - f"I'm sorry, that's not correct. Here's a hint: {hint}" - " Try guessing again!" - ), + "text": f"Good guess! {level}: {correct_name} is correct, but can you be more specific? Try guessing a smaller region or city.", } - else: + hint = self.get_hint(self.current_details["content"]) return { - "text": ( - "I'm sorry, I don't understand. Let's start a new game!" - ), + "text": f"I'm sorry, that's not correct. Here's a hint: {hint} Try guessing again!", } diff --git a/examples/game_geoguessr/geoguessr.py b/examples/game_geoguessr/geoguessr.py index 5ed181a4e..04f1e1ad6 100644 --- a/examples/game_geoguessr/geoguessr.py +++ b/examples/game_geoguessr/geoguessr.py @@ -1,9 +1,19 @@ # -*- coding: utf-8 -*- +import os +import json +from typing import List import requests -from typing import Dict, Any, List from agentscope.service import ServiceResponse, ServiceExecStatus +from agentscope.service import ServiceToolkit from agentscope.agents import DialogAgent import agentscope +from agentscope.message import Msg +from agentscope.agents import ReActAgent +from agentscope.service import ( + get_tripadvisor_location_photos, + search_tripadvisor, + get_tripadvisor_location_details, +) agentscope.init( # ... @@ -14,15 +24,6 @@ YOUR_MODEL_CONFIGURATION_NAME = "dashscope_chat-qwen-max" -# YOUR_MODEL_CONFIGURATION = { -# "model_type": "openai_chat", -# "config_name": "gpt-3.5-turbo", -# "model_name": "gpt-3.5-turbo", -# "api_key": "", # Load from env if not provided -# "generate_args": { -# "temperature": 0.7, -# }, -# } YOUR_MODEL_CONFIGURATION = [ { @@ -43,166 +44,110 @@ "temperature": 0.2, }, }, + { + "model_type": "openai_chat", + "config_name": "gpt-3.5-turbo", + "model_name": "gpt-4o", + "api_key": "", # Load from env if not provided + "generate_args": { + "temperature": 0.7, + }, + }, ] -from agentscope.service import ( - get_tripadvisor_location_photos, - search_tripadvisor, - get_tripadvisor_location_details, -) - -# def get_tripadvisor_location_photos(api_key: str, location_id: str, language: str = 'en') -> ServiceResponse: -# """ -# Get photos for a specific location using the TripAdvisor API and return the largest one. - -# Args: -# api_key (str): Your TripAdvisor API key. -# location_id (str): The location ID for the desired location. -# language (str, optional): The language for the response. Defaults to 'en'. - -# Returns: -# ServiceResponse: Contains the status and the response content with the largest photo. -# """ -# def find_largest_photo(photos: List[Dict[str, Any]]) -> Dict[str, Any]: -# """ -# Find the photo with the largest dimensions from the list of photos. - -# Args: -# photos (List[Dict[str, Any]]): List of photo data from TripAdvisor API. - -# Returns: -# Dict[str, Any]: The photo data with the largest dimensions. -# """ -# largest_photo_info = None -# max_area = 0 - -# for item in photos: -# for image_type, image_info in item['images'].items(): -# height = image_info['height'] -# width = image_info['width'] -# area = height * width -# if area > max_area: -# max_area = area -# largest_photo_info = { -# 'url': image_info['url'], -# 'height': height, -# 'width': width, -# 'caption': item.get('caption', ''), -# 'album': item.get('album', ''), -# 'published_date': item.get('published_date', ''), -# 'id': item.get('id', ''), -# 'source': item.get('source', {}), -# 'user': item.get('user', {}) -# } +def search_files_with_keywords( + directory: str, + keywords: List[str], +) -> List[str]: + """ + Search for filenames containing certain keywords within a given directory. + + Args: + directory (`str`): + The directory to search in. + keywords (`List[str]`): + A list of keywords to search for in filenames. + + Returns: + `List[str]`: A list of filenames containing any of the keywords. + + Example: + .. code-block:: python + + result = search_files_with_keywords( + "./my_directory", ["example", "test"] + ) + print("Files found:", result) + """ + matching_files = [] + try: + for root, _, files in os.walk(directory): + for file in files: + if any(keyword in file for keyword in keywords): + matching_files.append(os.path.join(root, file)) + + return matching_files + except Exception as e: + print(f"An error occurred: {e}") + return [] + + +def save_image_from_url( + url: str, +) -> ServiceResponse: + """ + Save an image from a URL to a specified local path. + + Args: + url (`str`): + The URL of the image to be downloaded. + Returns: + `ServiceResponse`: A dictionary with two variables: `status` and + `content`. The `status` variable is from the ServiceExecStatus enum, + and `content` is either the full path where the image has been saved + or error information, which depends on the `status` variable. + + Example: + .. code-block:: python + + result = save_image_from_url("http://example.com/image.jpg") + if result.status == ServiceExecStatus.SUCCESS: + print(f"Image saved at {result.content['path']}") + else: + print(f"Error: {result.content['error']}") + """ + try: + save_path = "./images" + os.makedirs(save_path, exist_ok=True) + response = requests.get(url, stream=True) + response.raise_for_status() # Check if the request was successful + + file_name = os.path.basename(url) + file_name = "image" + os.path.splitext(file_name)[1] + full_path = os.path.join(save_path, file_name) + + with open(full_path, "wb") as file: + for chunk in response.iter_content(chunk_size=8192): + file.write(chunk) + + # Save the URL to a .txt file + url_txt_path = os.path.join(save_path, "url.txt") + with open(url_txt_path, "w", encoding="utf-8") as txt_file: + txt_file.write(url) + + print(f"Image saved successfully at {full_path}") + return ServiceResponse( + status=ServiceExecStatus.SUCCESS, + content={"path": full_path}, + ) + except Exception as e: + print(f"An error occurred: {e}") + return ServiceResponse( + status=ServiceExecStatus.ERROR, + content={"error": str(e)}, + ) -# return largest_photo_info - - -# url = f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/photos?language={language}&key={api_key}" -# headers = { -# "accept": "application/json" -# } - -# try: -# response = requests.get(url, headers=headers) -# if response.status_code == 200: -# data = response.json() -# largest_photo = find_largest_photo(data['data']) -# return ServiceResponse( -# status=ServiceExecStatus.SUCCESS, -# content={"largest_photo": largest_photo} -# ) -# else: -# error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") -# return ServiceResponse( -# status=ServiceExecStatus.ERROR, -# content={"error": error_detail} -# ) -# except Exception as e: -# return ServiceResponse( -# status=ServiceExecStatus.ERROR, -# content={"error": str(e)} -# ) - - -# # Define the TripAdvisor API query function -# def search_tripadvisor(api_key: str, query: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: -# """ -# Search for locations using the TripAdvisor API. - -# Args: -# api_key (str): Your TripAdvisor API key. -# query (str): The search query. -# language (str, optional): The language for the response. Defaults to 'en'. -# currency (str, optional): The currency for the response. Defaults to 'USD'. - -# Returns: -# ServiceResponse: Contains the status and the response content. -# """ -# url = f"https://api.content.tripadvisor.com/api/v1/location/search?searchQuery={query}&language={language}&key={api_key}" -# headers = { -# "accept": "application/json" -# } - -# try: -# response = requests.get(url, headers=headers) -# if response.status_code == 200: -# return ServiceResponse( -# status=ServiceExecStatus.SUCCESS, -# content=response.json() -# ) -# else: -# error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") -# return ServiceResponse( -# status=ServiceExecStatus.ERROR, -# content={"error": error_detail} -# ) -# except Exception as e: -# return ServiceResponse( -# status=ServiceExecStatus.ERROR, -# content={"error": str(e)} -# ) - -# # Define the TripAdvisor location details query function -# def get_tripadvisor_location_details(api_key: str, location_id: str, language: str = 'en', currency: str = 'USD') -> ServiceResponse: -# """ -# Get details for a specific location using the TripAdvisor API. - -# Args: -# api_key (str): Your TripAdvisor API key. -# location_id (str): The location ID for the desired location. -# language (str, optional): The language for the response. Defaults to 'en'. -# currency (str, optional): The currency for the response. Defaults to 'USD'. - -# Returns: -# ServiceResponse: Contains the status and the response content. -# """ -# url = f"https://api.content.tripadvisor.com/api/v1/location/{location_id}/details?language={language}¤cy={currency}&key={api_key}" -# headers = { -# "accept": "application/json" -# } - -# try: -# response = requests.get(url, headers=headers) -# if response.status_code == 200: -# return ServiceResponse( -# status=ServiceExecStatus.SUCCESS, -# content=response.json() -# ) -# else: -# error_detail = response.json().get('error', {}).get('message', f"HTTP Error: {response.status_code}") -# return ServiceResponse( -# status=ServiceExecStatus.ERROR, -# content={"error": error_detail} -# ) -# except Exception as e: -# return ServiceResponse( -# status=ServiceExecStatus.ERROR, -# content={"error": str(e)} -# ) - -from agentscope.service import ServiceToolkit # Initialize the ServiceToolkit and register the TripAdvisor API functions service_toolkit = ServiceToolkit() @@ -218,53 +163,85 @@ get_tripadvisor_location_photos, api_key="", ) # Replace with your actual TripAdvisor API key -import json +service_toolkit.add( + save_image_from_url, +) # Replace with your actual TripAdvisor API key + print(json.dumps(service_toolkit.json_schemas, indent=4)) -from agentscope.agents import ReActAgent -import agentscope agentscope.init(model_configs=YOUR_MODEL_CONFIGURATION) gamemaster_agent = ReActAgent( name="gamemaster", - sys_prompt="""You are the gamemaster of a geoguessr-like game. You interacts with the player following the procedures below: -1. You propose a location (anywhere on earth, the mroe random and low-profile the better, diversify your proposal), then uses the tool `search_tripadvisor` to get its `location_id`; if multiple places are returned, you choose the one that matches what you proposed the most. -2. You use the method `get_tripadvisor_location_photos` to get the url of the image about this location, display the url as part of your output under 'speeak' (use '![image]' as alt text do not use the real name of the lcoation), and asks the player to guess which country/state/region/city/municipality this location is in. If the url is not available for this particular location, repeat step 1 and 2 until a valid image url is found. -3. You use the tool `get_tripadvisor_location_details` to get this location's details. -4. If the player's answer matches exactly the locaiton name correspond to the most precise `level` in `ancestors` in the returned details from step 2, they have won the game and game is over. Note that it is sufficient for the player to just give the exact location name to win the game. Congratulate the player. If the player's answer corresponds to other `level` in `ancestors` instead of the most precise one, encourage the player to give a more precise guess. if the player's answer does not matches the location name, nor any of the values of `name` in `ancestors` in the returned details from step 2, give the player a hint from the details from step 2 that is not too revealing, and ask the player to guess again. -Under no circumstances should you reveal the name of the location or of any object in the image before the player guesses it correctly. -When the game is over, append 'exit = True' to your last output.""", - model_config_name="dashscope_chat-qwen-max", + sys_prompt="""You are the gamemaster of a geoguessr-like game. + You interacts with the player following the procedures below: + 1. You propose a location (anywhere on earth, the mroe random + and low-profile the better, diversify your proposal), then + uses the tool `search_tripadvisor` to get its `location_id`; + if multiple places are returned, you choose the one that matches + what you proposed the most. + 2. You use the method `get_tripadvisor_location_photos` to get + the url of the image about this location, save the image with + the method `save_image_from_url` (do not tell the url directly + to the player; the player will be able to see the saved image), + and asks the player to guess which + country/state/region/city/municipality this location is in. + If the url is not available for this particular location, + repeat step 1 and 2 until a valid image url is found. + 3. You use the tool `get_tripadvisor_location_details` + to get this location's details. + 4. If the player's answer matches exactly the locaiton name + correspond to the most precise `level` in `ancestors` in the + returned details from step 2, they have won the game and game is over. + Note that it is sufficient for the player to just give the exact + location name to win the game. Congratulate the player. If the + player's answer corresponds to other `level` in `ancestors` instead + of the most precise one, encourage the player to give a + more precise guess. if the player's answer does not + matches the location name, nor any of the values of + `name` in `ancestors` in the returned details from step 2, + give the player a hint from the details from step 2 + that is not too revealing, and ask the player to guess again. + Under no circumstances should you reveal the name of the + location or of any object in the image before + the player guesses it correctly. When the game is over, + append 'exit = True' to your last output.""", + model_config_name="gpt-3.5-turbo", service_toolkit=service_toolkit, verbose=False, # set verbose to True to show the reasoning process ) player_agent = DialogAgent( name="player", - sys_prompt="""You're a player in a geoguessr-like turn-based game. Upon getting the url of an image from the gamemaster, you are supposed to guess where is the place shown in the image. Your guess can be a country, - a state, a region, a city, etc., but try to be as precise as possoble. If your answer is not correct, try again based on the hint given by the gamemaster.""", - model_config_name="dashscope_multimodal-qwen-vl-max", # replace by your model config name + sys_prompt="""You're a player in a geoguessr-like turn-based game. + Upon getting the url of an image from the gamemaster, you are + supposed to guess where is the place shown in the image. Your + guess can be a country, a state, a region, a city, etc., but try + to be as precise as possoble. If your answer is not correct, + try again based on the hint given by the gamemaster.""", + # replace by your model config name + model_config_name="dashscope_multimodal-qwen-vl-max", ) -# print("#"*80) -# print(agent.sys_prompt) -# print("#"*80) - -from agentscope.agents import UserAgent - -user = UserAgent(name="User") -from agentscope.message import Msg x = None -# x = Msg("Bob", "What about this picture I took?", url="https://media-cdn.tripadvisor.com/media/photo-w/1a/9e/7f/9d/eiffeltoren.jpg") -# gamemaster_agent.speak(x) +image_display_flag = 0 while True: x = gamemaster_agent(x) - # x['content'].pop('thought') - # x['url'] = x['content']['image_url'] + if "exit" in x.content or "congratulation" in x.content.lower(): break + if image_display_flag == 0: + images_path = search_files_with_keywords("./images", ["image"]) + x["url"] = images_path[0] + try: + with open("./images/url.txt", "r", encoding="utf-8") as f: + image_url = f.read().strip() + image_display_flag = 1 + except Exception as error: + print(f"An error occurred while reading the file: {error}") + y = Msg("system", "Image:", url=image_url) + gamemaster_agent.speak(y) x = player_agent(x) - # x = user(x) From dae9390e92ec9b4e687ddc0ff3607e36cee53091 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:55:05 +0800 Subject: [PATCH 09/24] updated neurosymbolic --- .../game_geoguessr/ExtendedDialogAgent.py | 668 ++++++++++++++++++ .../geoguesser_neurosymbolic.py | 529 +------------- 2 files changed, 681 insertions(+), 516 deletions(-) create mode 100644 examples/game_geoguessr/ExtendedDialogAgent.py diff --git a/examples/game_geoguessr/ExtendedDialogAgent.py b/examples/game_geoguessr/ExtendedDialogAgent.py new file mode 100644 index 000000000..f321c57b0 --- /dev/null +++ b/examples/game_geoguessr/ExtendedDialogAgent.py @@ -0,0 +1,668 @@ +from typing import Dict, Any, List, Optional, Tuple, Union +import os +import ast + +import requests + +from agentscope.service.service_response import ServiceExecStatus +from agentscope.agents.dialog_agent import DialogAgent as BaseDialogAgent + +class ExtendedDialogAgent(BaseDialogAgent): + """ + An extended dialog agent for managing a GeoGuessr-like game. + + This class extends the BaseDialogAgent to include additional functionality + specific to the geography guessing game. It manages game states, interacts + with external APIs for location information, and handles user inputs and + game logic. + + Attributes: + service_toolkit (ServiceToolkit): A toolkit for accessing + external services. + current_location (Optional[Dict[str, Any]]): The current location + being guessed. + current_details (Optional[Dict[str, Any]]): Detailed information + about the current location. + game_state (str): The current state of the game + (e.g., "start", "guessing", "end"). + + Methods: + parse_service_response: Parse the response from service calls. + propose_location: Generate a proposal for a new location. + search_and_select_location: Search for and select a location + based on a query. + get_location_details: Retrieve detailed information about a location. + get_location_photos: Get photos for a specific location. + check_guess_location: Check if a user's guess matches the actual + location. + check_guess_ancestors: Check if a user's guess matches any ancestor + locations. + save_image_from_url: Download and save an image from a URL. + find_largest_photo: Find the largest photo from a list of photos. + get_hint: Generate a hint about the current location. + handle_input: Process user input based on the current game state. + handle_start_state: Handle the initial state of the game. + handle_guessing_state: Handle the guessing state of the game. + """ + + def __init__( + self, + name: str, + sys_prompt: str, + model_config_name: str, + use_memory: bool = True, + memory_config: Optional[dict] = None, + ): + super().__init__( + name, + sys_prompt, + model_config_name, + use_memory, + memory_config, + ) + self.service_toolkit = service_toolkit + self.current_location = None + self.current_details = None + self.game_state = "start" + + def parse_service_response(self, response: str) -> dict: + """ + Parse the service response string and extract the JSON content. + Args: + response (str): The response string from the service call. + Returns: + dict: The parsed JSON content. + """ + result = {} + lines = response.split("\n") + for line in lines: + if "[STATUS]:" in line: + status = line.split("[STATUS]:")[-1].strip() + result["status"] = ( + ServiceExecStatus.SUCCESS + if status == "SUCCESS" + else ServiceExecStatus.ERROR + ) + if "[RESULT]:" in line: + json_str = line.split("[RESULT]:")[-1].strip() + result["content"] = ast.literal_eval(json_str) + return result + + def propose_location(self) -> str: + """ + Propose an interesting location for the player to guess. + + This method uses the model to generate a proposal for an interesting + location that the player will try to guess. It aims to diversify the + proposals and include less known locations. + + Returns: + str: The proposed location as a string. + """ + messages = [ + { + "role": "system", + "content": ( + "You are a game master for a geography guessing game. " + "Propose an interesting location for the player to guess. " + "Diversify your proposal and give less known ones." + ), + }, + { + "role": "user", + "content": ( + "Propose an interesting location for the player to guess. " + "Only output your final proposed location." + ), + }, + ] + response = self.model(messages).text.strip() + return response + + def search_and_select_location( + self, + proposed_location: str, + ) -> Optional[Dict[str, Any]]: + """ + Search for a location using TripAdvisor API and + select the most appropriate result. + + This method searches for a location using the + provided query, then either automatically selects + the first result or asks the model to choose the best + match if multiple results are returned. + + Args: + proposed_location (str): The location to search for. + + Returns: + dict: The selected location information, + or None if no location is found. + """ + response_str = self.service_toolkit.parse_and_call_func( + [ + { + "name": "search_tripadvisor", + "arguments": {"query": proposed_location}, + }, + ], + ) + result = self.parse_service_response(response_str) + if ( + result["status"] == ServiceExecStatus.SUCCESS + and result["content"]["data"] + ): + locations = result["content"]["data"] + if len(locations) > 1: + messages = [ + { + "role": "system", + "content": ( + "You are selecting the most appropriate location" + " from a list of search results." + ), + }, + { + "role": "user", + "content": ( + f"Select the location that best matches" + f" '{proposed_location}' from this list:\n" + + "\n".join( + [ + f"{i+1}. {loc['name']}, " + f"{loc.get('address_obj', {}).get('city', 'Unknown City')}, " # noqa: E501 # pylint: disable=line-too-long + f"{loc.get('address_obj',{}).get('country','Unknown Country')}" # noqa: E501 # pylint: disable=line-too-long + for i, loc in enumerate(locations) + ], + ) + + "\nRespond with only the number" + " of your selection." + ), + }, + ] + selection_response = self.model(messages).text.strip() + try: + # Try to extract the number from the response + selection = ( + int("".join(filter(str.isdigit, selection_response))) + - 1 + ) + if 0 <= selection < len(locations): + return locations[selection] + else: + # If the extracted number is out of range, + # return the first location + return locations[0] + except ValueError: + # If no number can be extracted, return the first location + return locations[0] + else: + return locations[0] + return None + + def get_location_details(self, location_id: str) -> Dict[str, Any]: + """ + Retrieve detailed information about a specific + location using its ID. + + This method calls the TripAdvisor API to get + detailed information about a location, + including its description, address, rating, + and other relevant data. + + Args: + location_id (str): The unique identifier of the + location in TripAdvisor's system. + + Returns: + Dict[str, Any]: A dictionary containing the parsed + response from the API call, including + the location details if the call was successful. + """ + response_str = self.service_toolkit.parse_and_call_func( + [ + { + "name": "get_tripadvisor_location_details", + "arguments": {"location_id": location_id}, + }, + ], + ) + return self.parse_service_response(response_str) + + def get_location_photos( + self, + location_id: str, + ) -> Optional[Dict[str, Any]]: + """ + Get the largest photo for a specific location. + + Args: + location_id (str): The location ID to get photos for. + + Returns: + dict: The largest photo information including the URL. + """ + logger.info( + "Calling TripAdvisor API for location photos: %s", + location_id, + ) + + response_str = self.service_toolkit.parse_and_call_func( + [ + { + "name": "get_tripadvisor_location_photos", + "arguments": {"location_id": location_id}, + }, + ], + ) + logger.info("TripAdvisor location photos result: %s", response_str) + result = self.parse_service_response(response_str) + largest_photo = self.find_largest_photo(result["content"]["data"]) + return ( + largest_photo + if result["status"] == ServiceExecStatus.SUCCESS + else None + ) + + def check_guess_location(self, user_guess: str, location: str) -> str: + """ + Check if the user's guess matches the actual location. + + This method uses the model to determine if the user's guess + refers to the same place as the actual location. + + Args: + user_guess (str): The location guessed by the user. + location (str): The actual location to compare against. + + Returns: + str: 'True' if the guess matches the location, 'False' otherwise. + """ + messages = [ + { + "role": "system", + "content": ( + "You are the gamemaster of a geoguessr game." + " Check if the player's guess: " + + str(user_guess) + + " means the same place/location as " + + str(location) + + ". If yes, return 'True'. Else, return 'False'." + " Only output one of the two " + "options given and nothing else." + ), + }, + {"role": "user", "content": user_guess}, + ] + + response = self.model(messages).text.strip() + logger.info("check_guess: %s", response) + return response + + def check_guess_ancestors( + self, + user_guess: str, + ancestors: List[Dict[str, Any]], + ) -> Tuple[str, str, str]: + """ + Check if the user's guess matches any of the location's ancestors. + + This method compares the user's guess against a list of ancestor + locations, determining if there's a match and identifying the + most specific (smallest level) correct guess. + + Args: + user_guess (str): The user's guess for the location. + ancestors (List[Dict[str, Any]]): A list of dictionaries containing + information about ancestor + locations. + + Returns: + Tuple[str, str, str]: A tuple containing: + - The level of the matched location (or "None" if no match) + - The name of the matched location (or "None" if no match) + - "True" if the match is the most specific, "False" otherwise + """ + matches = [] + + for location in ancestors: + if re.search( + r"\b" + re.escape(location["name"]) + r"\b", + user_guess, + re.IGNORECASE, + ): + matches.append(location) + if not matches: + return "None", "None", "False" + else: + messages = [ + { + "role": "system", + "content": ( + "Check if " + + str(matches) + + " include the smallest level in " + + str(ancestors) + + " based on their respective levels. If yes," + " return the smallest matched name and the " + "corresponding level as 'level,name,True'. " + "Else, return 'False'. Note that the placeholders " + "'level', 'name' in the output are to be replaced" + " by the actual values. Only output one " + "of the two options given and nothing else." + ), + }, + {"role": "user", "content": user_guess}, + ] + + response = self.model(messages).text.strip() + if "True" in response: + logger.info("check_guess: %s", response) + response = response.split(",") + return response[0], response[1], response[2] + else: + messages = [ + { + "role": "system", + "content": ( + "Return the smallest name based on their" + " respective 'level' values and the " + "corresponding 'level' values in " + + str(matches) + + " as 'level,name,False'. Note that the " + "placeholders 'level', 'name' in the output are" + " to be replaced by the actual values. " + "Only output in the given format and nothing else." + ), + }, + {"role": "user", "content": user_guess}, + ] + response = self.model(messages).text.strip() + logger.info("check_guess: %s", response) + response = response.split(",") + return response[0], response[1], response[2] + + def save_image_from_url(self, url: str, save_path: str) -> Optional[str]: + """ + Download and save an image from a given URL to a specified path. + + This method sends a GET request to the provided URL, downloads the + image, and saves it to the specified path. If successful, it returns + the full path where the image was saved. If an error occurs during + the process, it returns None. + + Args: + url (str): The URL of the image to download. + save_path (str): The directory path where the image + should be saved. + + Returns: + Optional[str]: The full path where the image was saved + if successful, or None if an error occurred. + """ + try: + os.makedirs(save_path, exist_ok=True) + # Send a HTTP request to the URL + response = requests.get(url, stream=True) + response.raise_for_status() # Check if the request was successful + + # Get the file name from the URL + file_name = os.path.basename(url) + file_name = "image" + os.path.splitext(file_name)[1] + # Full path to save the image + full_path = os.path.join(save_path, file_name) + + # Open a local file with write-binary mode + with open(full_path, "wb") as file: + for chunk in response.iter_content(chunk_size=8192): + file.write(chunk) + + print(f"Image saved successfully at {full_path}") + return full_path + except Exception as e: + print(f"An error occurred: {e}") + return None # Return None in case of an error + + def find_largest_photo( + self, + photos: List[Dict[str, Any]], + ) -> Dict[str, Any]: + """Find the photo with the largest + dimensions from the list of photos.""" + largest_photo_info = {} + max_area = 0 + + for item in photos: + for image_info in item["images"].values(): + height = image_info["height"] + width = image_info["width"] + area = height * width + + if area > max_area: + max_area = area + largest_photo_info = { + "url": image_info["url"], + "height": height, + "width": width, + "caption": item.get("caption", ""), + "album": item.get("album", ""), + "published_date": item.get("published_date", ""), + "id": item.get("id", ""), + "source": item.get("source", {}), + "user": item.get("user", {}), + } + + return largest_photo_info + + def get_hint(self, details: Dict[str, Any]) -> str: + """ + Generate a hint about the location based on the provided details. + + This method uses the model to create a hint that gives the user some + information about the location without making it too obvious. + + Args: + details (Dict[str, Any]): A dictionary containing details + about the location. + + Returns: + str: A hint about the location. + """ + messages = [ + { + "role": "system", + "content": ( + "You're a game master for a geography guessing game." + ), + }, + { + "role": "user", + "content": ( + f"give the user some hint about the location" + f" based on the info from {details}. " + "Don't make it too obvious." + ), + }, + ] + response = self.model(messages).text.strip() + logger.info("check_guess: %s", response) + return response + + def handle_input(self, user_input: dict) -> dict: + """ + Handle user input based on the current game state. + + This method processes the user's input and returns an appropriate + response based on the current state of the game (start, guessing, + or other). + + Args: + user_input (dict): A dictionary containing the user's input. + It should have a 'text' key with the user's + message. + + Returns: + dict: A dictionary containing the response to the user's input. + The response format depends on the current game state. + """ + query = user_input["text"] + response = {} + + if self.game_state == "start": + response = self.handle_start_state() + elif self.game_state == "guessing": + response = self.handle_guessing_state(query) + else: + response = { + "text": ( + "I'm sorry, I don't understand. " "Let's start a new game!" + ), + } + + return response + + def handle_start_state( + self, + ) -> Union[Dict[str, str], List[Dict[str, Union[str, Optional[str]]]]]: + """ + Handle the start state of the game. + + This method is responsible for initializing the game by + selecting a location, retrieving its details and photos, + and preparing the initial response to the player. + + Returns: + Union[Dict[str, str], List[Dict[str, Union[str, Optional[str]]]]]: + A dictionary with a text response if there's an error, or a list + containing dictionaries with text response, image path, and + image URL for display. + """ + photos: Any = None + while not photos: + proposed_location = self.propose_location() + self.current_location = self.search_and_select_location( + proposed_location, + ) + print("self.current_location: ", self.current_location) + + if not self.current_location: + return { + "text": ( + "I'm sorry, I couldn't find a suitable location." + " Let's try again." + ), + } + + self.current_details = self.get_location_details( + self.current_location["location_id"], + ) + if self.current_details["status"] != ServiceExecStatus.SUCCESS: + return { + "text": ( + "I'm having trouble getting details for this location." + " Let's try again." + ), + } + + ancestors = self.current_details["content"].get("ancestors", []) + print("ancestors: ", ancestors) + + photos = self.get_location_photos( + self.current_location["location_id"], + ) + + response = ( + "Let's play a geography guessing game! I've chosen a location" + " and displayed an image of it. Can you guess which country, " + "state, region, city, or municipality this location is in?" + ) + self.game_state = "guessing" + if isinstance(photos, dict) and isinstance(photos.get("url"), str): + image_path = self.save_image_from_url( + photos["url"], + save_path="./images", + ) + return [ + {"text": response}, + {"image": image_path}, + {"image_for_display": photos["url"]}, + ] + else: + return {"text": response} + + def handle_guessing_state(self, query: str) -> Dict[str, str]: + """ + Handle the guessing state of the game. + + This method processes the user's guess, checks if it's correct, + and provides appropriate feedback or hints. + + Args: + query (str): The user's guess. + + Returns: + Dict[str, str]: A dictionary containing the response text. + """ + if self.current_location is None: + return { + "text": ( + "I'm sorry, there seems to be an issue" + " with the current location. Let's start a new game." + ), + } + + if ( + self.check_guess_location( + query.lower(), + self.current_location["name"].lower(), + ) + == "True" + ): + self.game_state = "end" + return { + "text": ( + f"Congratulations! You've guessed correctly. The location" + f" is indeed in {self.current_location['name']}." + ), + } + + if ( + self.current_details is None + or "content" not in self.current_details + ): + return { + "text": ( + "I'm sorry, there seems to be an issue" + " with the location details. Let's start a new game." + ), + } + + ancestors = self.current_details["content"].get("ancestors", []) + level, correct_name, is_smallest = self.check_guess_ancestors( + query, + ancestors, + ) + + if level != "None": + if is_smallest == "True": + self.game_state = "end" + return { + "text": ( + f"Congratulations! You've guessed correctly. " + f"The location is indeed in {level}: {correct_name}." + ), + } + else: + return { + "text": f"Good guess! {level}: {correct_name}" + f" is correct, but can you be more specific? " + f"Try guessing a smaller region or city.", + } + else: + hint = self.get_hint(self.current_details["content"]) + return { + "text": ( + f"I'm sorry, that's not correct. Here's a hint: {hint} " + "Try guessing again!" + ), + } \ No newline at end of file diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py index 047f88162..3c9b0be6f 100644 --- a/examples/game_geoguessr/geoguesser_neurosymbolic.py +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -1,33 +1,28 @@ # -*- coding: utf-8 -*- +""" +This module implements a GeoGuessr-like game using AgentScope. + +It includes classes and functions for managing the game state, interacting with +external APIs (like TripAdvisor), and handling user inputs. The game involves +a gamemaster agent that selects locations and provides hints, and a player +agent that tries to guess the locations based on images and clues. +""" from typing import Dict, Any, List, Optional, Tuple, Union -import os import re -import ast import logging -from io import BytesIO -from IPython.display import display -import requests -from PIL import Image as PILImage - -from agentscope.service.service_response import ( - ServiceResponse, - ServiceExecStatus, -) from agentscope.service.service_toolkit import ServiceToolkit import agentscope from agentscope.message import Msg -from agentscope.agents.dialog_agent import DialogAgent as BaseDialogAgent from agentscope.agents import DialogAgent from agentscope.service import ( get_tripadvisor_location_photos, search_tripadvisor, get_tripadvisor_location_details, ) - from agentscope.parsers import ParserBase from agentscope.models import ModelResponse - +import ExtendedDialogAgent class LocationMatcherParser(ParserBase): """ @@ -98,505 +93,7 @@ def parse(self, response: ModelResponse) -> ModelResponse: ) # Replace with your actual TripAdvisor API key -class ExtendedDialogAgent(BaseDialogAgent): - def __init__( - self, - name: str, - sys_prompt: str, - model_config_name: str, - use_memory: bool = True, - memory_config: Optional[dict] = None, - ): - super().__init__( - name, - sys_prompt, - model_config_name, - use_memory, - memory_config, - ) - self.service_toolkit = service_toolkit - self.current_location = None - self.current_details = None - self.game_state = "start" - - def parse_service_response(self, response: str) -> dict: - """ - Parse the service response string and extract the JSON content. - Args: - response (str): The response string from the service call. - Returns: - dict: The parsed JSON content. - """ - result = {} - lines = response.split("\n") - for line in lines: - if "[STATUS]:" in line: - status = line.split("[STATUS]:")[-1].strip() - result["status"] = ( - ServiceExecStatus.SUCCESS - if status == "SUCCESS" - else ServiceExecStatus.ERROR - ) - if "[RESULT]:" in line: - json_str = line.split("[RESULT]:")[-1].strip() - result["content"] = ast.literal_eval(json_str) - return result - - def propose_location(self) -> str: - """ - Propose an interesting location for the player to guess. - - This method uses the model to generate a proposal for an interesting - location that the player will try to guess. It aims to diversify the - proposals and include less known locations. - - Returns: - str: The proposed location as a string. - """ - messages = [ - { - "role": "system", - "content": ( - "You are a game master for a geography guessing game. " - "Propose an interesting location for the player to guess. " - "Diversify your proposal and give less known ones." - ), - }, - { - "role": "user", - "content": ( - "Propose an interesting location for the player to guess. " - "Only output your final proposed location." - ), - }, - ] - response = self.model(messages).text.strip() - return response - - def search_and_select_location( - self, - proposed_location: str, - ) -> Optional[Dict[str, Any]]: - """ - Search for a location using TripAdvisor API and select the most appropriate result. - - This method searches for a location using the provided query, then either - automatically selects the first result or asks the model to choose the best - match if multiple results are returned. - - Args: - proposed_location (str): The location to search for. - - Returns: - dict: The selected location information, or None if no location is found. - """ - response_str = self.service_toolkit.parse_and_call_func( - [ - { - "name": "search_tripadvisor", - "arguments": {"query": proposed_location}, - }, - ], - ) - result = self.parse_service_response(response_str) - if ( - result["status"] == ServiceExecStatus.SUCCESS - and result["content"]["data"] - ): - locations = result["content"]["data"] - if len(locations) > 1: - messages = [ - { - "role": "system", - "content": ( - "You are selecting the most appropriate location" - " from a list of search results." - ), - }, - { - "role": "user", - "content": ( - f"Select the location that best matches" - f" '{proposed_location}' from this list:\n" - + "\n".join( - [ - f"{i+1}. {loc['name']}, " - f"{loc.get('address_obj', {}).get('city', 'Unknown City')}, " - f"{loc.get('address_obj',{}).get('country','Unknown Country')}" - for i, loc in enumerate(locations) - ], - ) - + "\nRespond with only the number" - " of your selection." - ), - }, - ] - selection_response = self.model(messages).text.strip() - try: - # Try to extract the number from the response - selection = ( - int("".join(filter(str.isdigit, selection_response))) - - 1 - ) - if 0 <= selection < len(locations): - return locations[selection] - else: - # If the extracted number is out of range, - # return the first location - return locations[0] - except ValueError: - # If no number can be extracted, return the first location - return locations[0] - else: - return locations[0] - return None - - def get_location_details(self, location_id: str) -> Dict[str, Any]: - response_str = self.service_toolkit.parse_and_call_func( - [ - { - "name": "get_tripadvisor_location_details", - "arguments": {"location_id": location_id}, - }, - ], - ) - return self.parse_service_response(response_str) - - def get_location_photos( - self, - location_id: str, - ) -> Optional[Dict[str, Any]]: - """ - Get the largest photo for a specific location. - - Args: - location_id (str): The location ID to get photos for. - - Returns: - dict: The largest photo information including the URL. - """ - logger.info( - "Calling TripAdvisor API for location photos: %s", - location_id, - ) - - response_str = self.service_toolkit.parse_and_call_func( - [ - { - "name": "get_tripadvisor_location_photos", - "arguments": {"location_id": location_id}, - }, - ], - ) - logger.info("TripAdvisor location photos result: %s", response_str) - result = self.parse_service_response(response_str) - largest_photo = self.find_largest_photo(result["content"]["data"]) - return ( - largest_photo - if result["status"] == ServiceExecStatus.SUCCESS - else None - ) - - def display_photo(self, location_id: str) -> bool: - largest_photo = self.get_location_photos(location_id) - - if largest_photo: - try: - image_url = largest_photo["url"] - response = requests.get(image_url) - img = PILImage.open(BytesIO(response.content)) - display(img) - return True - except Exception as e: - logger.error("Error displaying image: %s", str(e)) - return False - - def check_guess_location(self, user_guess: str, location: str) -> str: - messages = [ - { - "role": "system", - "content": ( - "You are the gamemaster of a geoguessr game." - " Check if the player's guess: " - + str(user_guess) - + " means the same place/location as " - + str(location) - + ". If yes, return 'True'. Else, return 'False'." - " Only output one of the two " - "options given and nothing else." - ), - }, - {"role": "user", "content": user_guess}, - ] - - response = self.model(messages).text.strip() - logger.info("check_guess: %s", response) - return response - - def check_guess_ancestors( - self, - user_guess: str, - ancestors: List[Dict[str, Any]], - ) -> Tuple[str, str, str]: - matches = [] - - for location in ancestors: - if re.search( - r"\b" + re.escape(location["name"]) + r"\b", - user_guess, - re.IGNORECASE, - ): - matches.append(location) - if not matches: - return "None", "None", "False" - else: - messages = [ - { - "role": "system", - "content": ( - "Check if " - + str(matches) - + " include the smallest level in " - + str(ancestors) - + " based on their respective levels. If yes," - " return the smallest matched name and the " - "corresponding level as 'level,name,True'. " - "Else, return 'False'. Note that the placeholders " - "'level', 'name' in the output are to be replaced" - " by the actual values. Only output one " - "of the two options given and nothing else." - ), - }, - {"role": "user", "content": user_guess}, - ] - - response = self.model(messages).text.strip() - if "True" in response: - logger.info("check_guess: %s", response) - response = response.split(",") - return response[0], response[1], response[2] - else: - messages = [ - { - "role": "system", - "content": ( - "Return the smallest name based on their" - " respective 'level' values and the " - "corresponding 'level' values in " - + str(matches) - + " as 'level,name,False'. Note that the " - "placeholders 'level', 'name' in the output are" - " to be replaced by the actual values. " - "Only output in the given format and nothing else." - ), - }, - {"role": "user", "content": user_guess}, - ] - response = self.model(messages).text.strip() - logger.info("check_guess: %s", response) - response = response.split(",") - return response[0], response[1], response[2] - - def save_image_from_url(self, url: str, save_path: str) -> Optional[str]: - try: - os.makedirs(save_path, exist_ok=True) - # Send a HTTP request to the URL - response = requests.get(url, stream=True) - response.raise_for_status() # Check if the request was successful - - # Get the file name from the URL - file_name = os.path.basename(url) - file_name = "image" + os.path.splitext(file_name)[1] - # Full path to save the image - full_path = os.path.join(save_path, file_name) - - # Open a local file with write-binary mode - with open(full_path, "wb") as file: - for chunk in response.iter_content(chunk_size=8192): - file.write(chunk) - - print(f"Image saved successfully at {full_path}") - return full_path - except Exception as e: - print(f"An error occurred: {e}") - return None # Return None in case of an error - - def find_largest_photo( - self, - photos: List[Dict[str, Any]], - ) -> Dict[str, Any]: - """Find the photo with the largest - dimensions from the list of photos.""" - largest_photo_info = {} - max_area = 0 - - for item in photos: - for image_info in item["images"].values(): - height = image_info["height"] - width = image_info["width"] - area = height * width - - if area > max_area: - max_area = area - largest_photo_info = { - "url": image_info["url"], - "height": height, - "width": width, - "caption": item.get("caption", ""), - "album": item.get("album", ""), - "published_date": item.get("published_date", ""), - "id": item.get("id", ""), - "source": item.get("source", {}), - "user": item.get("user", {}), - } - - return largest_photo_info - - def get_hint(self, details: Dict[str, Any]) -> str: - """ - Generate a hint about the location based on the provided details. - - This method uses the model to create a hint that gives the user some - information about the location without making it too obvious. - - Args: - details (Dict[str, Any]): A dictionary containing details about the location. - - Returns: - str: A hint about the location. - """ - messages = [ - { - "role": "system", - "content": ( - "You're a game master for a geography guessing game." - ), - }, - { - "role": "user", - "content": ( - f"give the user some hint about the location" - f" based on the info from {details}. " - "Don't make it too obvious." - ), - }, - ] - response = self.model(messages).text.strip() - logger.info("check_guess: %s", response) - return response - - def handle_input(self, user_input: dict) -> dict: - query = user_input["text"] - response = {} - - if self.game_state == "start": - response = self.handle_start_state() - elif self.game_state == "guessing": - response = self.handle_guessing_state(query) - else: - response = { - "text": "I'm sorry, I don't understand. Let's start a new game!", - } - - return response - - def handle_start_state( - self, - ) -> Union[Dict[str, str], List[Dict[str, Union[str, Optional[str]]]]]: - photo_displayed = False - photos: Any = None - while not photo_displayed: - proposed_location = self.propose_location() - self.current_location = self.search_and_select_location( - proposed_location, - ) - print("self.current_location: ", self.current_location) - - if not self.current_location: - return { - "text": "I'm sorry, I couldn't find a suitable location. Let's try again.", - } - - self.current_details = self.get_location_details( - self.current_location["location_id"], - ) - if self.current_details["status"] != ServiceExecStatus.SUCCESS: - return { - "text": "I'm having trouble getting details for this location. Let's try again.", - } - - ancestors = self.current_details["content"].get("ancestors", []) - print("ancestors: ", ancestors) - - photo_displayed = self.display_photo( - self.current_location["location_id"], - ) - photos = self.get_location_photos( - self.current_location["location_id"], - ) - - response = "Let's play a geography guessing game! I've chosen a location and displayed an image of it. Can you guess which country, state, region, city, or municipality this location is in?" - self.game_state = "guessing" - if isinstance(photos, dict) and isinstance(photos.get("url"), str): - image_path = self.save_image_from_url( - photos["url"], - save_path="./images", - ) - return [ - {"text": response}, - {"image": image_path}, - {"image_for_display": photos["url"]}, - ] - else: - return {"text": response} - - def handle_guessing_state(self, query: str) -> Dict[str, str]: - if self.current_location is None: - return { - "text": "I'm sorry, there seems to be an issue with the current location. Let's start a new game.", - } - - if ( - self.check_guess_location( - query.lower(), - self.current_location["name"].lower(), - ) - == "True" - ): - self.game_state = "end" - return { - "text": f"Congratulations! You've guessed correctly. The location is indeed in {self.current_location['name']}.", - } - - if ( - self.current_details is None - or "content" not in self.current_details - ): - return { - "text": "I'm sorry, there seems to be an issue with the location details. Let's start a new game.", - } - - ancestors = self.current_details["content"].get("ancestors", []) - level, correct_name, is_smallest = self.check_guess_ancestors( - query, - ancestors, - ) - if level != "None": - if is_smallest == "True": - self.game_state = "end" - return { - "text": f"Congratulations! You've guessed correctly. The location is indeed in {level}: {correct_name}.", - } - else: - return { - "text": f"Good guess! {level}: {correct_name} is correct, but can you be more specific? Try guessing a smaller region or city.", - } - else: - hint = self.get_hint(self.current_details["content"]) - return { - "text": f"I'm sorry, that's not correct. Here's a hint: {hint} Try guessing again!", - } # Initialize AgentScope and run @@ -638,7 +135,7 @@ def main() -> None: "model_name": "gpt-4o", "api_key": "", "generate_args": { - "temperature": 0.8, + "temperature": 1.5, }, }, ], @@ -660,9 +157,9 @@ def main() -> None: a state, a region, a city, etc., but try to be as precise as possoble. If your answer is not correct, try again based on the hint given by the gamemaster.""", - model_config_name=( - "dashscope_multimodal-qwen-vl-max" - ), # replace by your model config name + model_config_name=("dashscope_multimodal-qwen-vl-max"), + # Replace by your model config name. + # The model needs to be have vision modality. ) # Start the game From 24b00ecb012ca7c21bfe1aec2579899cbdfb2f7f Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:59:38 +0800 Subject: [PATCH 10/24] Update geoguesser_neurosymbolic.py --- .../geoguesser_neurosymbolic.py | 55 ++----------------- 1 file changed, 4 insertions(+), 51 deletions(-) diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py index 3c9b0be6f..acf4bfeb0 100644 --- a/examples/game_geoguessr/geoguesser_neurosymbolic.py +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -7,11 +7,7 @@ a gamemaster agent that selects locations and provides hints, and a player agent that tries to guess the locations based on images and clues. """ -from typing import Dict, Any, List, Optional, Tuple, Union -import re -import logging -from agentscope.service.service_toolkit import ServiceToolkit import agentscope from agentscope.message import Msg from agentscope.agents import DialogAgent @@ -20,50 +16,8 @@ search_tripadvisor, get_tripadvisor_location_details, ) -from agentscope.parsers import ParserBase -from agentscope.models import ModelResponse -import ExtendedDialogAgent - -class LocationMatcherParser(ParserBase): - """ - A parser to match locations in natural language text - against a predefined list of locations. - """ - - def __init__(self, locations: List[Dict[str, Any]]): - """ - Initialize the parser with a list of location dictionaries. - - Args: - locations (list): A list of dictionaries, - each containing location information. - """ - self.locations = locations - - def parse(self, response: ModelResponse) -> ModelResponse: - """ - Parse the response text to find matches with the predefined locations. - - Args: - response (ModelResponse): The response object containing - the text to parse. - - Returns: - ModelResponse: The response object with the parsed result added. - """ - text = response.text - matches = [] - - for location in self.locations: - if re.search( - r"\b" + re.escape(location["name"]) + r"\b", - text, - re.IGNORECASE, - ): - matches.append(location) - - response.parsed = matches - return response +from agentscope.service.service_toolkit import ServiceToolkit +from ExtendedDialogAgent import ExtendedDialogAgent agentscope.init( @@ -72,9 +26,7 @@ def parse(self, response: ModelResponse) -> ModelResponse: name="xxx", studio_url="http://127.0.0.1:5000", # The URL of AgentScope Studio ) -# Setup logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) + # Initialize the ServiceToolkit and register the TripAdvisor API functions @@ -146,6 +98,7 @@ def main() -> None: name="GeoGuessr Gamemaster", sys_prompt="You're a game master for a geography guessing game.", model_config_name="gpt-4o_config", + service_toolkit=service_toolkit, use_memory=True, ) From 9831665719c0521ed080f7de559fbb0b112b8b9a Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Wed, 7 Aug 2024 20:02:32 +0800 Subject: [PATCH 11/24] updated neurosymbolic --- .../game_geoguessr/ExtendedDialogAgent.py | 22 +++++++++++++------ .../geoguesser_neurosymbolic.py | 4 ---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/game_geoguessr/ExtendedDialogAgent.py b/examples/game_geoguessr/ExtendedDialogAgent.py index f321c57b0..49444b37e 100644 --- a/examples/game_geoguessr/ExtendedDialogAgent.py +++ b/examples/game_geoguessr/ExtendedDialogAgent.py @@ -1,12 +1,22 @@ +# -*- coding: utf-8 -*- from typing import Dict, Any, List, Optional, Tuple, Union import os import ast +import logging +import re import requests +from agentscope.service.service_toolkit import ServiceToolkit from agentscope.service.service_response import ServiceExecStatus from agentscope.agents.dialog_agent import DialogAgent as BaseDialogAgent + +# Setup logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + class ExtendedDialogAgent(BaseDialogAgent): """ An extended dialog agent for managing a GeoGuessr-like game. @@ -50,6 +60,7 @@ def __init__( name: str, sys_prompt: str, model_config_name: str, + service_toolkit: ServiceToolkit, use_memory: bool = True, memory_config: Optional[dict] = None, ): @@ -546,12 +557,9 @@ def handle_start_state( print("self.current_location: ", self.current_location) if not self.current_location: - return { - "text": ( - "I'm sorry, I couldn't find a suitable location." - " Let's try again." - ), - } + raise ValueError( + "Make sure the correct API keys are provided." + ) self.current_details = self.get_location_details( self.current_location["location_id"], @@ -665,4 +673,4 @@ def handle_guessing_state(self, query: str) -> Dict[str, str]: f"I'm sorry, that's not correct. Here's a hint: {hint} " "Try guessing again!" ), - } \ No newline at end of file + } diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py index acf4bfeb0..d62963be7 100644 --- a/examples/game_geoguessr/geoguesser_neurosymbolic.py +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -28,7 +28,6 @@ ) - # Initialize the ServiceToolkit and register the TripAdvisor API functions service_toolkit = ServiceToolkit() service_toolkit.add( @@ -45,9 +44,6 @@ ) # Replace with your actual TripAdvisor API key - - - # Initialize AgentScope and run def main() -> None: """A GeoGuessr-like game demo""" From 73b45ad787b1becabf69fb99fd14efb4fa6551f6 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Wed, 7 Aug 2024 20:15:08 +0800 Subject: [PATCH 12/24] updated geoguessr --- examples/game_geoguessr/ExtendedDialogAgent.py | 16 +++++++++++++++- examples/game_geoguessr/README.md | 3 +++ .../game_geoguessr/geoguesser_neurosymbolic.py | 5 +++-- examples/game_geoguessr/geoguessr.py | 4 ++++ 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 examples/game_geoguessr/README.md diff --git a/examples/game_geoguessr/ExtendedDialogAgent.py b/examples/game_geoguessr/ExtendedDialogAgent.py index 49444b37e..43a196b28 100644 --- a/examples/game_geoguessr/ExtendedDialogAgent.py +++ b/examples/game_geoguessr/ExtendedDialogAgent.py @@ -1,4 +1,18 @@ # -*- coding: utf-8 -*- +""" +This module contains the ExtendedDialogAgent class, which is an extension of +the BaseDialogAgent class for managing a GeoGuessr-like game. + +The ExtendedDialogAgent class provides functionality for: +- Managing game states +- Interacting with external APIs for location information +- Handling user inputs and game logic +- Generating location proposals and hints +- Processing and validating user guesses + +This module is part of a larger project aimed at creating an interactive +geography guessing game using AI-powered dialogue agents. +""" from typing import Dict, Any, List, Optional, Tuple, Union import os import ast @@ -558,7 +572,7 @@ def handle_start_state( if not self.current_location: raise ValueError( - "Make sure the correct API keys are provided." + "Make sure the correct API keys are provided.", ) self.current_details = self.get_location_details( diff --git a/examples/game_geoguessr/README.md b/examples/game_geoguessr/README.md new file mode 100644 index 000000000..fd82427f3 --- /dev/null +++ b/examples/game_geoguessr/README.md @@ -0,0 +1,3 @@ +use agentscope studio to visualize the game + +generative version vs neurosynblic version \ No newline at end of file diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py index d62963be7..ce0d88deb 100644 --- a/examples/game_geoguessr/geoguesser_neurosymbolic.py +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """ -This module implements a GeoGuessr-like game using AgentScope. +This module implements the neurosymbolic version of a GeoGuessr-like game +using AgentScope. It includes classes and functions for managing the game state, interacting with external APIs (like TripAdvisor), and handling user inputs. The game involves @@ -8,6 +9,7 @@ agent that tries to guess the locations based on images and clues. """ +from ExtendedDialogAgent import ExtendedDialogAgent import agentscope from agentscope.message import Msg from agentscope.agents import DialogAgent @@ -17,7 +19,6 @@ get_tripadvisor_location_details, ) from agentscope.service.service_toolkit import ServiceToolkit -from ExtendedDialogAgent import ExtendedDialogAgent agentscope.init( diff --git a/examples/game_geoguessr/geoguessr.py b/examples/game_geoguessr/geoguessr.py index 04f1e1ad6..1bbd14f0f 100644 --- a/examples/game_geoguessr/geoguessr.py +++ b/examples/game_geoguessr/geoguessr.py @@ -1,4 +1,8 @@ # -*- coding: utf-8 -*- +""" +This module implements the generative version of a Geoguessr-like game using +the AgentScope framework. +""" import os import json from typing import List From b85747369da5035d64ab8a882bc84b3aa6116f45 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:25:58 +0800 Subject: [PATCH 13/24] updated --- examples/game_geoguessr/README.md | 42 ++++++++++++++++++- .../geoguesser_neurosymbolic.py | 9 ---- examples/game_geoguessr/geoguessr.py | 4 +- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/examples/game_geoguessr/README.md b/examples/game_geoguessr/README.md index fd82427f3..183a85b9f 100644 --- a/examples/game_geoguessr/README.md +++ b/examples/game_geoguessr/README.md @@ -1,3 +1,41 @@ -use agentscope studio to visualize the game +# GeoGuessr-like Game with AgentScope -generative version vs neurosynblic version \ No newline at end of file +This example will show +- How to build a GeoGuessr-like game with AgentScope. +- How to use AgentScope Studio to visualize conversations between the agents. +- How to use the TripAdvisor API as tool functions in AgentScope. +- How to build a neurosymbolic agent and a generative agent in AgentScope. + + +## Background + +This example demonstrates two different implementations of a GeoGuessr-like game using the AgentScope framework. + +**Neurosymbolic Version:** This version features a gamemaster agent that selects locations and provides hints, and a player agent that tries to guess the locations based on images and clues. The gamemaster agent utilizes a rule-based-LLM hybrid approach to manage the game state and interact with the TripAdvisor API. + +**Generative Version:** This version leverages the capabilities of a large language model (LLM) to handle the game logic and interaction with the player. The gamemaster agent, powered by an LLM, dynamically selects locations, retrieves images, and provides hints based on the player's guesses. + +Both versions showcase the flexibility and power of AgentScope in building interactive and engaging AI applications. + +## Tested Models + +These models are tested in this example in the exact same way they are used in the examples. For other models, some modifications may be needed. +- gpt-4o +- qwen-vl-max + + +## Prerequisites + +- **AgentScope:** Install the latest AgentScope library. +- **TripAdvisor API Key:** Obtain an API key from TripAdvisor and replace the placeholders in the code with your actual key. +- **OpenAI API Key (Optional):** If you want to use OpenAI models, obtain an API key from OpenAI and configure it accordingly. +- **Dashscope API Key (Optional):** If you want to use Dashscope models, obtain an API key from Dashscope and configure it accordingly. +- **Required Python Packages:** +``` +requests +``` +- **Access to the internet:** Required to interact with the TripAdvisor API and download images. + + +Both examples have been set to run with AgentScope Studio by default. After starting running the script, open http://127.0.0.1:5000/ with your brower (Chorme is preferred). +For more information on AgentScope Studio see http://doc.agentscope.io/en/tutorial/209-gui.html \ No newline at end of file diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py index ce0d88deb..df68f910a 100644 --- a/examples/game_geoguessr/geoguesser_neurosymbolic.py +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -51,15 +51,6 @@ def main() -> None: agentscope.init( model_configs=[ - { - "model_type": "openai_chat", - "config_name": "gpt-3.5-turbo", - "model_name": "gpt-3.5-turbo", - "api_key": "", # Load from env if not provided - "generate_args": { - "temperature": 0.5, - }, - }, { "config_name": "dashscope_chat-qwen-max", "model_type": "dashscope_chat", diff --git a/examples/game_geoguessr/geoguessr.py b/examples/game_geoguessr/geoguessr.py index 1bbd14f0f..bec09c3a7 100644 --- a/examples/game_geoguessr/geoguessr.py +++ b/examples/game_geoguessr/geoguessr.py @@ -50,7 +50,7 @@ }, { "model_type": "openai_chat", - "config_name": "gpt-3.5-turbo", + "config_name": "gpt", "model_name": "gpt-4o", "api_key": "", # Load from env if not provided "generate_args": { @@ -212,7 +212,7 @@ def save_image_from_url( location or of any object in the image before the player guesses it correctly. When the game is over, append 'exit = True' to your last output.""", - model_config_name="gpt-3.5-turbo", + model_config_name="gpt", service_toolkit=service_toolkit, verbose=False, # set verbose to True to show the reasoning process ) From 6df41080264f28d753b156b21cf236a4f8ac8117 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:29:57 +0800 Subject: [PATCH 14/24] upadted --- examples/game_geoguessr/README.md | 12 ++++++------ .../{geoguessr.py => geoguessr_generative.py} | 0 2 files changed, 6 insertions(+), 6 deletions(-) rename examples/game_geoguessr/{geoguessr.py => geoguessr_generative.py} (100%) diff --git a/examples/game_geoguessr/README.md b/examples/game_geoguessr/README.md index 183a85b9f..ffc02fea9 100644 --- a/examples/game_geoguessr/README.md +++ b/examples/game_geoguessr/README.md @@ -9,9 +9,9 @@ This example will show ## Background -This example demonstrates two different implementations of a GeoGuessr-like game using the AgentScope framework. +This example demonstrates two different implementations of a GeoGuessr-like game using the AgentScope framework. -**Neurosymbolic Version:** This version features a gamemaster agent that selects locations and provides hints, and a player agent that tries to guess the locations based on images and clues. The gamemaster agent utilizes a rule-based-LLM hybrid approach to manage the game state and interact with the TripAdvisor API. +**Neurosymbolic Version:** This version features a gamemaster agent that selects locations and provides hints, and a player agent that tries to guess the locations based on images and clues. The gamemaster agent utilizes a rule-based-LLM hybrid approach to manage the game state and interact with the TripAdvisor API. `ExtendedDialogAgent.py` implements the agent with all the logics needed. **Generative Version:** This version leverages the capabilities of a large language model (LLM) to handle the game logic and interaction with the player. The gamemaster agent, powered by an LLM, dynamically selects locations, retrieves images, and provides hints based on the player's guesses. @@ -30,12 +30,12 @@ These models are tested in this example in the exact same way they are used in t - **TripAdvisor API Key:** Obtain an API key from TripAdvisor and replace the placeholders in the code with your actual key. - **OpenAI API Key (Optional):** If you want to use OpenAI models, obtain an API key from OpenAI and configure it accordingly. - **Dashscope API Key (Optional):** If you want to use Dashscope models, obtain an API key from Dashscope and configure it accordingly. -- **Required Python Packages:** +- **Required Python Packages:** ``` requests ``` - **Access to the internet:** Required to interact with the TripAdvisor API and download images. - -Both examples have been set to run with AgentScope Studio by default. After starting running the script, open http://127.0.0.1:5000/ with your brower (Chorme is preferred). -For more information on AgentScope Studio see http://doc.agentscope.io/en/tutorial/209-gui.html \ No newline at end of file +## Running the Example +Both examples have been set to run with AgentScope Studio by default. After starting running the script, open http://127.0.0.1:5000/ with your browser (Chorme is preferred). +For more information on AgentScope Studio see http://doc.agentscope.io/en/tutorial/209-gui.html \ No newline at end of file diff --git a/examples/game_geoguessr/geoguessr.py b/examples/game_geoguessr/geoguessr_generative.py similarity index 100% rename from examples/game_geoguessr/geoguessr.py rename to examples/game_geoguessr/geoguessr_generative.py From 1487f2d85b8411d2f88e9993a59f5653ae8d3098 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:14:15 +0800 Subject: [PATCH 15/24] Update README.md --- examples/game_geoguessr/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/game_geoguessr/README.md b/examples/game_geoguessr/README.md index ffc02fea9..098948b4a 100644 --- a/examples/game_geoguessr/README.md +++ b/examples/game_geoguessr/README.md @@ -37,5 +37,5 @@ requests - **Access to the internet:** Required to interact with the TripAdvisor API and download images. ## Running the Example -Both examples have been set to run with AgentScope Studio by default. After starting running the script, open http://127.0.0.1:5000/ with your browser (Chorme is preferred). +Both examples have been set to run with AgentScope Studio by default. After starting running the script, open http://127.0.0.1:5000 with your browser (Chorme is preferred). For more information on AgentScope Studio see http://doc.agentscope.io/en/tutorial/209-gui.html \ No newline at end of file From 9e3b95f4e9454d4c521ec2c9dd6f080f53d8b301 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:29:21 +0800 Subject: [PATCH 16/24] Update README.md --- examples/game_geoguessr/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/game_geoguessr/README.md b/examples/game_geoguessr/README.md index 098948b4a..76bc26c4d 100644 --- a/examples/game_geoguessr/README.md +++ b/examples/game_geoguessr/README.md @@ -37,5 +37,5 @@ requests - **Access to the internet:** Required to interact with the TripAdvisor API and download images. ## Running the Example -Both examples have been set to run with AgentScope Studio by default. After starting running the script, open http://127.0.0.1:5000 with your browser (Chorme is preferred). -For more information on AgentScope Studio see http://doc.agentscope.io/en/tutorial/209-gui.html \ No newline at end of file +Both examples have been set to run with AgentScope Studio by default. After starting running the script, open `http://127.0.0.1:5000` with your browser (Chorme is preferred). +For more information on AgentScope Studio see `http://doc.agentscope.io/en/tutorial/209-gui.html` \ No newline at end of file From 592620ca339b4166cc54492cccd2d5a65f675ddc Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:07:01 +0800 Subject: [PATCH 17/24] Update 204-service.md --- docs/sphinx_doc/en/source/tutorial/204-service.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sphinx_doc/en/source/tutorial/204-service.md b/docs/sphinx_doc/en/source/tutorial/204-service.md index 38b4047b0..d54f1053d 100644 --- a/docs/sphinx_doc/en/source/tutorial/204-service.md +++ b/docs/sphinx_doc/en/source/tutorial/204-service.md @@ -30,6 +30,9 @@ The following table outlines the various Service functions by type. These functi | | `dblp_search_publications` | Search publications in the DBLP database | | `dblp_search_authors` | Search for author information in the DBLP database | | | `dblp_search_venues` | Search for venue information in the DBLP database | +| | `get_tripadvisor_location_photos` | Retrieve photos for a specific location using the TripAdvisor API. | +| | `search_tripadvisor` | Search for locations using the TripAdvisor API. | +| | `get_tripadvisor_location_details` | Get detailed information about a specific location using the TripAdvisor API. | | File | `create_file` | Create a new file at a specified path, optionally with initial content. | | | `delete_file` | Delete a file specified by a file path. | | | `move_file` | Move or rename a file from one path to another. | From e56f05182f36df95ebd024d870a57ade7f2f3e28 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:09:39 +0800 Subject: [PATCH 18/24] Update 204-service.md --- docs/sphinx_doc/zh_CN/source/tutorial/204-service.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/204-service.md b/docs/sphinx_doc/zh_CN/source/tutorial/204-service.md index 23c145a05..ea7b4597f 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/204-service.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/204-service.md @@ -27,6 +27,9 @@ | | `dblp_search_publications` | 在dblp数据库里搜索文献。 | | `dblp_search_authors` | 在dblp数据库里搜索作者。 | | | `dblp_search_venues` | 在dblp数据库里搜索期刊,会议及研讨会。 | +| | `get_tripadvisor_location_photos` | 使用 TripAdvisor API 检索特定位置的照片。 | +| | `search_tripadvisor` | 使用 TripAdvisor API 搜索位置。 | +| | `get_tripadvisor_location_details` | 使用 TripAdvisor API 获取特定位置的详细信息。 | | 文件处理 | `create_file` | 在指定路径创建一个新文件,并可选择添加初始内容。 | | | `delete_file` | 删除由文件路径指定的文件。 | | | `move_file` | 将文件从一个路径移动或重命名到另一个路径。 | From 41f3f75b046d6093c86f8c3b0b3db197933d9c32 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:18:16 +0800 Subject: [PATCH 19/24] Revert "Update 204-service.md" This reverts commit e56f05182f36df95ebd024d870a57ade7f2f3e28. --- .../en/source/tutorial/204-service.md | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/docs/sphinx_doc/en/source/tutorial/204-service.md b/docs/sphinx_doc/en/source/tutorial/204-service.md index d54f1053d..1dff87f95 100644 --- a/docs/sphinx_doc/en/source/tutorial/204-service.md +++ b/docs/sphinx_doc/en/source/tutorial/204-service.md @@ -12,49 +12,48 @@ AgentScope and how to use them to enhance the capabilities of your agents. The following table outlines the various Service functions by type. These functions can be called using `agentscope.service.{function_name}`. -| Service Scene | Service Function Name | Description | -|-----------------------------|----------------------------|----------------------------------------------------------------------------------------------------------------| -| Code | `execute_python_code` | Execute a piece of Python code, optionally inside a Docker container. | -| Retrieval | `retrieve_from_list` | Retrieve a specific item from a list based on given criteria. | -| | `cos_sim` | Compute the cosine similarity between two different embeddings. | -| SQL Query | `query_mysql` | Execute SQL queries on a MySQL database and return results. | -| | `query_sqlite` | Execute SQL queries on a SQLite database and return results. | -| | `query_mongodb` | Perform queries or operations on a MongoDB collection. | -| Text Processing | `summarization` | Summarize a piece of text using a large language model to highlight its main points. | -| Web | `bing_search` | Perform bing search | -| | `google_search` | Perform google search | -| | `arxiv_search` | Perform arXiv search | -| | `download_from_url` | Download file from given URL. | -| | `load_web` | Load and parse the web page of the specified url (currently only supports HTML). | -| | `digest_webpage` | Digest the content of a already loaded web page (currently only supports HTML). -| | `dblp_search_publications` | Search publications in the DBLP database -| | `dblp_search_authors` | Search for author information in the DBLP database | -| | `dblp_search_venues` | Search for venue information in the DBLP database | -| | `get_tripadvisor_location_photos` | Retrieve photos for a specific location using the TripAdvisor API. | -| | `search_tripadvisor` | Search for locations using the TripAdvisor API. | -| | `get_tripadvisor_location_details` | Get detailed information about a specific location using the TripAdvisor API. | -| File | `create_file` | Create a new file at a specified path, optionally with initial content. | -| | `delete_file` | Delete a file specified by a file path. | -| | `move_file` | Move or rename a file from one path to another. | -| | `create_directory` | Create a new directory at a specified path. | -| | `delete_directory` | Delete a directory and all its contents. | -| | `move_directory` | Move or rename a directory from one path to another. | -| | `read_text_file` | Read and return the content of a text file. | -| | `write_text_file` | Write text content to a file at a specified path. | -| | `read_json_file` | Read and parse the content of a JSON file. | -| | `write_json_file` | Serialize a Python object to JSON and write to a file. | -| Multi Modality | `dashscope_text_to_image` | Convert text to image using Dashscope API. | -| | `dashscope_image_to_text` | Convert image to text using Dashscope API. | -| | `dashscope_text_to_audio` | Convert text to audio using Dashscope API. | -| | `openai_text_to_image` | Convert text to image using OpenAI API -| | `openai_edit_image` | Edit an image based on the provided mask and prompt using OpenAI API -| | `openai_create_image_variation` | Create variations of an image using OpenAI API -| | `openai_image_to_text` | Convert text to image using OpenAI API -| | `openai_text_to_audio` | Convert text to audio using OpenAI API -| | `openai_audio_to_text` | Convert audio to text using OpenAI API - - -| *More services coming soon* | | More service functions are in development and will be added to AgentScope to further enhance its capabilities. | +| Service Scene | Service Function Name | Description | +|-----------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| Code | `execute_python_code` | Execute a piece of Python code, optionally inside a Docker container. | +| | `NoteBookExecutor` | Compute Execute a segment of Python code in the IPython environment of the NoteBookExecutor, adhering to the IPython interactive computing style. | +| Retrieval | `retrieve_from_list` | Retrieve a specific item from a list based on given criteria. | +| | `cos_sim` | Compute the cosine similarity between two different embeddings. | +| SQL Query | `query_mysql` | Execute SQL queries on a MySQL database and return results. | +| | `query_sqlite` | Execute SQL queries on a SQLite database and return results. | +| | `query_mongodb` | Perform queries or operations on a MongoDB collection. | +| Text Processing | `summarization` | Summarize a piece of text using a large language model to highlight its main points. | +| Web | `bing_search` | Perform bing search | +| | `google_search` | Perform google search | +| | `arxiv_search` | Perform arXiv search | +| | `download_from_url` | Download file from given URL. | +| | `load_web` | Load and parse the web page of the specified url (currently only supports HTML). | +| | `digest_webpage` | Digest the content of a already loaded web page (currently only supports HTML). | +| | `dblp_search_publications` | Search publications in the DBLP database | +| | `dblp_search_authors` | Search for author information in the DBLP database | +| | `dblp_search_venues` | Search for venue information in the DBLP database | +| | `tripadvisor_search` | Search for locations using the TripAdvisor API. | +| | `tripadvisor_search_location_photos` | Retrieve photos for a specific location using the TripAdvisor API. | +| | `tripadvisor_search_location_details` | Get detailed information about a specific location using the TripAdvisor API. | +| File | `create_file` | Create a new file at a specified path, optionally with initial content. | +| | `delete_file` | Delete a file specified by a file path. | +| | `move_file` | Move or rename a file from one path to another. | +| | `create_directory` | Create a new directory at a specified path. | +| | `delete_directory` | Delete a directory and all its contents. | +| | `move_directory` | Move or rename a directory from one path to another. | +| | `read_text_file` | Read and return the content of a text file. | +| | `write_text_file` | Write text content to a file at a specified path. | +| | `read_json_file` | Read and parse the content of a JSON file. | +| | `write_json_file` | Serialize a Python object to JSON and write to a file. | +| Multi Modality | `dashscope_text_to_image` | Convert text to image using Dashscope API. | +| | `dashscope_image_to_text` | Convert image to text using Dashscope API. | +| | `dashscope_text_to_audio` | Convert text to audio using Dashscope API. | +| | `openai_text_to_image` | Convert text to image using OpenAI API | +| | `openai_edit_image` | Edit an image based on the provided mask and prompt using OpenAI API | +| | `openai_create_image_variation` | Create variations of an image using OpenAI API | +| | `openai_image_to_text` | Convert text to image using OpenAI API | +| | `openai_text_to_audio` | Convert text to audio using OpenAI API | +| | `openai_audio_to_text` | Convert audio to text using OpenAI API | +| *More services coming soon* | | More service functions are in development and will be added to AgentScope to further enhance its capabilities. | About each service function, you can find detailed information in the [API document](https://modelscope.github.io/agentscope/). @@ -332,4 +331,4 @@ class YourAgent(AgentBase): # ... [omitted for brevity] ``` -[[Return to Top]](#204-service-en) +[[Return to Top]](#204-service-en) \ No newline at end of file From 114e888bec48e6831de1e531f828ba7fe299a643 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:19:57 +0800 Subject: [PATCH 20/24] Update 204-service.md --- .../zh_CN/source/tutorial/204-service.md | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/204-service.md b/docs/sphinx_doc/zh_CN/source/tutorial/204-service.md index ea7b4597f..4bf011e2e 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/204-service.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/204-service.md @@ -9,47 +9,48 @@ 下面的表格按照类型概述了各种Service函数。以下函数可以通过`agentscope.service.{函数名}`进行调用。 -| Service场景 | Service函数名称 | 描述 | -|------------|-----------------------|-----------------------------------------| -| 代码 | `execute_python_code` | 执行一段 Python 代码,可选择在 Docker
容器内部执行。 | -| 检索 | `retrieve_from_list` | 根据给定的标准从列表中检索特定项目。 | -| | `cos_sim` | 计算2个embedding的余弦相似度。 | -| SQL查询 | `query_mysql` | 在 MySQL 数据库上执行 SQL 查询并返回结果。 | -| | `query_sqlite` | 在 SQLite 数据库上执行 SQL 查询并返回结果。 | -| | `query_mongodb` | 对 MongoDB 集合执行查询或操作。 | -| 文本处理 | `summarization` | 使用大型语言模型总结一段文字以突出其主要要点。 | -| 网络 | `bing_search` | 使用bing搜索。 | -| | `google_search` | 使用google搜索。 | -| | `arxiv_search` | 使用arxiv搜索。 | -| | `download_from_url` | 从指定的 URL 下载文件。 | -| | `load_web` | 爬取并解析指定的网页链接 (目前仅支持爬取 HTML 页面) | -| | `digest_webpage` | 对已经爬取好的网页生成摘要信息(目前仅支持 HTML 页面 -| | `dblp_search_publications` | 在dblp数据库里搜索文献。 -| | `dblp_search_authors` | 在dblp数据库里搜索作者。 | -| | `dblp_search_venues` | 在dblp数据库里搜索期刊,会议及研讨会。 | -| | `get_tripadvisor_location_photos` | 使用 TripAdvisor API 检索特定位置的照片。 | -| | `search_tripadvisor` | 使用 TripAdvisor API 搜索位置。 | -| | `get_tripadvisor_location_details` | 使用 TripAdvisor API 获取特定位置的详细信息。 | -| 文件处理 | `create_file` | 在指定路径创建一个新文件,并可选择添加初始内容。 | -| | `delete_file` | 删除由文件路径指定的文件。 | -| | `move_file` | 将文件从一个路径移动或重命名到另一个路径。 | -| | `create_directory` | 在指定路径创建一个新的目录。 | -| | `delete_directory` | 删除一个目录及其所有内容。 | -| | `move_directory` | 将目录从一个路径移动或重命名到另一个路径。 | -| | `read_text_file` | 读取并返回文本文件的内容。 | -| | `write_text_file` | 向指定路径的文件写入文本内容。 | -| | `read_json_file` | 读取并解析 JSON 文件的内容。 | -| | `write_json_file` | 将 Python 对象序列化为 JSON 并写入到文件。 | -| 多模态 | `dashscope_text_to_image` | 使用 DashScope API 将文本生成图片。 | -| | `dashscope_image_to_text` | 使用 DashScope API 根据图片生成文字。 | -| | `dashscope_text_to_audio` | 使用 DashScope API 根据文本生成音频。 | -| | `openai_text_to_image` | 使用 OpenAI API根据文本生成图片。 -| | `openai_edit_image` | 使用 OpenAI API 根据提供的遮罩和提示编辑图像。 -| | `openai_create_image_variation` | 使用 OpenAI API 创建图像的变体。 -| | `openai_image_to_text` | 使用 OpenAI API 根据图片生成文字。 -| | `openai_text_to_audio` | 使用 OpenAI API 根据文本生成音频。 -| | `openai_audio_to_text` | 使用OpenAI API将音频转换为文本。 -| *更多服务即将推出* | | 正在开发更多服务功能,并将添加到 AgentScope 以进一步增强其能力。 | +| Service场景 | Service函数名称 | 描述 | +|------------|---------------------------------------|--------------------------------------------------------------------| +| 代码 | `execute_python_code` | 执行一段 Python 代码,可选择在 Docker 容器内部执行。 | +| | `NoteBookExecutor` | 在 NoteBookExecutor 的 IPython 环境中执行一段 Python 代码,遵循 IPython 交互式计算风格。 | +| 检索 | `retrieve_from_list` | 根据给定的标准从列表中检索特定项目。 | +| | `cos_sim` | 计算2个embedding的余弦相似度。 | +| SQL查询 | `query_mysql` | 在 MySQL 数据库上执行 SQL 查询并返回结果。 | +| | `query_sqlite` | 在 SQLite 数据库上执行 SQL 查询并返回结果。 | +| | `query_mongodb` | 对 MongoDB 集合执行查询或操作。 | +| 文本处理 | `summarization` | 使用大型语言模型总结一段文字以突出其主要要点。 | +| 网络 | `bing_search` | 使用bing搜索。 | +| | `google_search` | 使用google搜索。 | +| | `arxiv_search` | 使用arxiv搜索。 | +| | `download_from_url` | 从指定的 URL 下载文件。 | +| | `load_web` | 爬取并解析指定的网页链接 (目前仅支持爬取 HTML 页面) | +| | `digest_webpage` | 对已经爬取好的网页生成摘要信息(目前仅支持 HTML 页面) | +| | `dblp_search_publications` | 在dblp数据库里搜索文献。 | +| | `dblp_search_authors` | 在dblp数据库里搜索作者。 | +| | `dblp_search_venues` | 在dblp数据库里搜索期刊,会议及研讨会。 | +| | `tripadvisor_search` | 使用 TripAdvisor API 搜索位置。 | +| | `tripadvisor_search_location_photos` | 使用 TripAdvisor API 检索特定位置的照片。 | +| | `tripadvisor_search_location_details` | 使用 TripAdvisor API 获取特定位置的详细信息。 | +| 文件处理 | `create_file` | 在指定路径创建一个新文件,并可选择添加初始内容。 | +| | `delete_file` | 删除由文件路径指定的文件。 | +| | `move_file` | 将文件从一个路径移动或重命名到另一个路径。 | +| | `create_directory` | 在指定路径创建一个新的目录。 | +| | `delete_directory` | 删除一个目录及其所有内容。 | +| | `move_directory` | 将目录从一个路径移动或重命名到另一个路径。 | +| | `read_text_file` | 读取并返回文本文件的内容。 | +| | `write_text_file` | 向指定路径的文件写入文本内容。 | +| | `read_json_file` | 读取并解析 JSON 文件的内容。 | +| | `write_json_file` | 将 Python 对象序列化为 JSON 并写入到文件。 | +| 多模态 | `dashscope_text_to_image` | 使用 DashScope API 将文本生成图片。 | +| | `dashscope_image_to_text` | 使用 DashScope API 根据图片生成文字。 | +| | `dashscope_text_to_audio` | 使用 DashScope API 根据文本生成音频。 | +| | `openai_text_to_image` | 使用 OpenAI API根据文本生成图片。 | +| | `openai_edit_image` | 使用 OpenAI API 根据提供的遮罩和提示编辑图像。 | +| | `openai_create_image_variation` | 使用 OpenAI API 创建图像的变体。 | +| | `openai_image_to_text` | 使用 OpenAI API 根据图片生成文字。 | +| | `openai_text_to_audio` | 使用 OpenAI API 根据文本生成音频。 | +| | `openai_audio_to_text` | 使用OpenAI API将音频转换为文本。 | +| *更多服务即将推出* | | 正在开发更多服务功能,并将添加到 AgentScope 以进一步增强其能力。 | 关于详细的参数、预期输入格式、返回类型,请参阅[API文档](https://modelscope.github.io/agentscope/)。 @@ -308,4 +309,4 @@ class YourAgent(AgentBase): # ... [为简洁起见省略代码] ``` -[[返回顶部]](#204-service-zh) +[[返回顶部]](#204-service-zh) \ No newline at end of file From a05a50177fa87792eb22b77d00fa1627aa3c7b36 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:22:30 +0800 Subject: [PATCH 21/24] update naming of tripadvisor tool functions --- .../game_geoguessr/ExtendedDialogAgent.py | 6 ++--- .../geoguesser_neurosymbolic.py | 12 +++++----- .../game_geoguessr/geoguessr_generative.py | 18 +++++++------- src/agentscope/service/__init__.py | 12 +++++----- src/agentscope/service/web/tripadvisor.py | 24 +++++++++---------- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/examples/game_geoguessr/ExtendedDialogAgent.py b/examples/game_geoguessr/ExtendedDialogAgent.py index 43a196b28..4c06bc4fb 100644 --- a/examples/game_geoguessr/ExtendedDialogAgent.py +++ b/examples/game_geoguessr/ExtendedDialogAgent.py @@ -167,7 +167,7 @@ def search_and_select_location( response_str = self.service_toolkit.parse_and_call_func( [ { - "name": "search_tripadvisor", + "name": "tripadvisor_search", "arguments": {"query": proposed_location}, }, ], @@ -247,7 +247,7 @@ def get_location_details(self, location_id: str) -> Dict[str, Any]: response_str = self.service_toolkit.parse_and_call_func( [ { - "name": "get_tripadvisor_location_details", + "name": "tripadvisor_search_location_details", "arguments": {"location_id": location_id}, }, ], @@ -275,7 +275,7 @@ def get_location_photos( response_str = self.service_toolkit.parse_and_call_func( [ { - "name": "get_tripadvisor_location_photos", + "name": "tripadvisor_search_location_photos", "arguments": {"location_id": location_id}, }, ], diff --git a/examples/game_geoguessr/geoguesser_neurosymbolic.py b/examples/game_geoguessr/geoguesser_neurosymbolic.py index df68f910a..00ae4a313 100644 --- a/examples/game_geoguessr/geoguesser_neurosymbolic.py +++ b/examples/game_geoguessr/geoguesser_neurosymbolic.py @@ -14,9 +14,9 @@ from agentscope.message import Msg from agentscope.agents import DialogAgent from agentscope.service import ( - get_tripadvisor_location_photos, - search_tripadvisor, - get_tripadvisor_location_details, + tripadvisor_search_location_photos, + tripadvisor_search, + tripadvisor_search_location_details, ) from agentscope.service.service_toolkit import ServiceToolkit @@ -32,15 +32,15 @@ # Initialize the ServiceToolkit and register the TripAdvisor API functions service_toolkit = ServiceToolkit() service_toolkit.add( - search_tripadvisor, + tripadvisor_search, api_key="", ) # Replace with your actual TripAdvisor API key service_toolkit.add( - get_tripadvisor_location_details, + tripadvisor_search_location_details, api_key="", ) # Replace with your actual TripAdvisor API key service_toolkit.add( - get_tripadvisor_location_photos, + tripadvisor_search_location_photos, api_key="", ) # Replace with your actual TripAdvisor API key diff --git a/examples/game_geoguessr/geoguessr_generative.py b/examples/game_geoguessr/geoguessr_generative.py index bec09c3a7..fc8f40804 100644 --- a/examples/game_geoguessr/geoguessr_generative.py +++ b/examples/game_geoguessr/geoguessr_generative.py @@ -14,9 +14,9 @@ from agentscope.message import Msg from agentscope.agents import ReActAgent from agentscope.service import ( - get_tripadvisor_location_photos, - search_tripadvisor, - get_tripadvisor_location_details, + tripadvisor_search_location_photos, + tripadvisor_search, + tripadvisor_search_location_details, ) agentscope.init( @@ -156,15 +156,15 @@ def save_image_from_url( # Initialize the ServiceToolkit and register the TripAdvisor API functions service_toolkit = ServiceToolkit() service_toolkit.add( - search_tripadvisor, + tripadvisor_search, api_key="", ) # Replace with your actual TripAdvisor API key service_toolkit.add( - get_tripadvisor_location_details, + tripadvisor_search_location_details, api_key="", ) # Replace with your actual TripAdvisor API key service_toolkit.add( - get_tripadvisor_location_photos, + tripadvisor_search_location_photos, api_key="", ) # Replace with your actual TripAdvisor API key service_toolkit.add( @@ -183,10 +183,10 @@ def save_image_from_url( You interacts with the player following the procedures below: 1. You propose a location (anywhere on earth, the mroe random and low-profile the better, diversify your proposal), then - uses the tool `search_tripadvisor` to get its `location_id`; + uses the tool `tripadvisor_search` to get its `location_id`; if multiple places are returned, you choose the one that matches what you proposed the most. - 2. You use the method `get_tripadvisor_location_photos` to get + 2. You use the method `tripadvisor_search_location_photos` to get the url of the image about this location, save the image with the method `save_image_from_url` (do not tell the url directly to the player; the player will be able to see the saved image), @@ -194,7 +194,7 @@ def save_image_from_url( country/state/region/city/municipality this location is in. If the url is not available for this particular location, repeat step 1 and 2 until a valid image url is found. - 3. You use the tool `get_tripadvisor_location_details` + 3. You use the tool `tripadvisor_search_location_details` to get this location's details. 4. If the player's answer matches exactly the locaiton name correspond to the most precise `level` in `ancestors` in the diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 74c87f5df..1d2b7cf2c 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -22,9 +22,9 @@ from .web.search import bing_search, google_search from .web.arxiv import arxiv_search from .web.tripadvisor import ( - get_tripadvisor_location_photos, - search_tripadvisor, - get_tripadvisor_location_details, + tripadvisor_search_location_photos, + tripadvisor_search, + tripadvisor_search_location_details, ) from .web.dblp import ( dblp_search_publications, @@ -106,9 +106,9 @@ def get_help() -> None: "openai_image_to_text", "openai_edit_image", "openai_create_image_variation", - "get_tripadvisor_location_photos", - "search_tripadvisor", - "get_tripadvisor_location_details", + "tripadvisor_search_location_photos", + "tripadvisor_search", + "tripadvisor_search_location_details", # to be deprecated "ServiceFactory", ] diff --git a/src/agentscope/service/web/tripadvisor.py b/src/agentscope/service/web/tripadvisor.py index b07cdaf4c..e6e7bacc1 100644 --- a/src/agentscope/service/web/tripadvisor.py +++ b/src/agentscope/service/web/tripadvisor.py @@ -7,7 +7,7 @@ from agentscope.service.service_status import ServiceExecStatus -def get_tripadvisor_location_photos( +def tripadvisor_search_location_photos( api_key: str, location_id: str = None, query: str = None, @@ -82,14 +82,14 @@ def get_tripadvisor_location_photos( .. code-block:: python # Using location_id - result = get_tripadvisor_location_photos( + result = tripadvisor_search_location_photos( "your_api_key", location_id="123456", language="en" ) if result.status == ServiceExecStatus.SUCCESS: print(result.content) # Or using a query - result = get_tripadvisor_location_photos( + result = tripadvisor_search_location_photos( "your_api_key", query="Eiffel Tower", language="en" ) if result.status == ServiceExecStatus.SUCCESS: @@ -135,8 +135,8 @@ def get_tripadvisor_location_photos( raise ValueError("Either location_id or query must be provided.") if location_id is None: - # Use search_tripadvisor to get the location_id - search_result = search_tripadvisor(api_key, query, language) + # Use tripadvisor_search to get the location_id + search_result = tripadvisor_search(api_key, query, language) if search_result.status != ServiceExecStatus.SUCCESS: return search_result @@ -200,7 +200,7 @@ def get_tripadvisor_location_photos( ) -def search_tripadvisor( +def tripadvisor_search( api_key: str, query: str, language: str = "en", @@ -248,7 +248,7 @@ def search_tripadvisor( Example: .. code-block:: python - result = search_tripadvisor("your_api_key", "Socotra", "en") + result = tripadvisor_search("your_api_key", "Socotra", "en") if result.status == ServiceExecStatus.SUCCESS: print(result.content) @@ -323,7 +323,7 @@ def search_tripadvisor( ) -def get_tripadvisor_location_details( +def tripadvisor_search_location_details( api_key: str, location_id: str = None, query: str = None, @@ -366,7 +366,7 @@ def get_tripadvisor_location_details( .. code-block:: python # Using location_id - result = get_tripadvisor_location_details( + result = tripadvisor_search_location_details( "your_api_key", location_id="574818", language="en", @@ -376,7 +376,7 @@ def get_tripadvisor_location_details( print(result.content) # Or using a query - result = get_tripadvisor_location_details( + result = tripadvisor_search_location_details( "your_api_key", query="Socotra Island", language="en", @@ -460,8 +460,8 @@ def get_tripadvisor_location_details( raise ValueError("Either location_id or query must be provided.") if location_id is None: - # Use search_tripadvisor to get the location_id - search_result = search_tripadvisor(api_key, query, language) + # Use tripadvisor_search to get the location_id + search_result = tripadvisor_search(api_key, query, language) if search_result.status != ServiceExecStatus.SUCCESS: return search_result From 87e9ff19e0c61e95476ce08a5ddee51d928a74c1 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:32:21 +0800 Subject: [PATCH 22/24] Update tripadvisor.py update tool functions naming according to merged tripadvisor tool functions naming convention --- src/agentscope/service/web/tripadvisor.py | 110 ++++++++++++---------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/src/agentscope/service/web/tripadvisor.py b/src/agentscope/service/web/tripadvisor.py index e6e7bacc1..a47ac2d6f 100644 --- a/src/agentscope/service/web/tripadvisor.py +++ b/src/agentscope/service/web/tripadvisor.py @@ -3,8 +3,8 @@ from loguru import logger import requests -from agentscope.service.service_response import ServiceResponse -from agentscope.service.service_status import ServiceExecStatus +from ..service_response import ServiceResponse +from ..service_status import ServiceExecStatus def tripadvisor_search_location_photos( @@ -20,8 +20,8 @@ def tripadvisor_search_location_photos( api_key (`str`): Your TripAdvisor API key. location_id (`str`, optional): - The ID of the location for which to retrieve photos. - Required if query is not provided. + The unique identifier for a location on Tripadvisor. The location + ID can be obtained using the tripadvisor_search function query (`str`, optional): The search query to find a location. Required if location_id is not provided. @@ -36,43 +36,55 @@ def tripadvisor_search_location_photos( If successful, the `content` will be a dictionary with the following structure: - { - 'photo_data': { - 'data': [ - { - 'id': int, - 'is_blessed': bool, - 'caption': str, - 'published_date': str, - 'images': { - 'thumbnail': { - 'height': int, - 'width': int, - 'url': str - }, - 'small': {'height': int, 'width': int, 'url': str}, - 'medium': { - 'height': int, - 'width': int, - 'url': str + + .. code-block:: json + + { + 'photo_data': { + 'data': [ + { + 'id': int, + 'is_blessed': bool, + 'caption': str, + 'published_date': str, + 'images': { + 'thumbnail': { + 'height': int, + 'width': int, + 'url': str + }, + 'small': { + 'height': int, + 'width': int, + 'url': str + }, + 'medium': { + 'height': int, + 'width': int, + 'url': str + }, + 'large': { + 'height': int, + 'width': int, + 'url': str + }, + 'original': { + 'height': int, + 'width': int, + 'url': str + } }, - 'large': {'height': int, 'width': int, 'url': str}, - 'original': { - 'height': int, - 'width': int, - 'url': str - } + 'album': str, + 'source': {'name': str, 'localized_name': str}, + 'user': {'username': str} }, - 'album': str, - 'source': {'name': str, 'localized_name': str}, - 'user': {'username': str} - }, - ... - ] + ... + ] + } } - } - Each item in the 'data' list represents - a photo associated with the location. + + Each item in the 'data' list represents a photo associated with the + location. Note: Either `location_id` or `query` must be provided. If both are provided, @@ -171,7 +183,7 @@ def tripadvisor_search_location_photos( logger.info(f"Requesting photos for location ID {location_id}") try: - response = requests.get(url, headers=headers) + response = requests.get(url, headers=headers, timeout=20) logger.info( f"Received response with status code {response.status_code}", ) @@ -294,7 +306,7 @@ def tripadvisor_search( logger.info(f"Searching for locations with query '{query}'") try: - response = requests.get(url, headers=headers) + response = requests.get(url, headers=headers, timeout=20) logger.info( f"Received response with status code {response.status_code}", ) @@ -331,8 +343,7 @@ def tripadvisor_search_location_details( currency: str = "USD", ) -> ServiceResponse: """ - Get detailed information about a specific location - using the TripAdvisor API. + Get detailed information about a specific location using the TripAdvisor API. Args: api_key (`str`): @@ -344,9 +355,10 @@ def tripadvisor_search_location_details( The search query to find a location. Required if location_id is not provided. language (`str`, optional): - The language for the response. Defaults to 'en'. + The language for the response. Defaults to 'en', 'zh' for Chinese. currency (`str`, optional): - The currency for pricing information. Defaults to 'USD'. + The currency code to use for request and response + (should follow ISO 4217). Defaults to 'USD'. Returns: `ServiceResponse`: A dictionary with two variables: `status` and @@ -366,7 +378,7 @@ def tripadvisor_search_location_details( .. code-block:: python # Using location_id - result = tripadvisor_search_location_details( + result = get_tripadvisor_location_details( "your_api_key", location_id="574818", language="en", @@ -376,7 +388,7 @@ def tripadvisor_search_location_details( print(result.content) # Or using a query - result = tripadvisor_search_location_details( + result = get_tripadvisor_location_details( "your_api_key", query="Socotra Island", language="en", @@ -455,7 +467,7 @@ def tripadvisor_search_location_details( Raises: ValueError: If neither location_id nor query is provided. - """ + """ # noqa if location_id is None and query is None: raise ValueError("Either location_id or query must be provided.") @@ -495,7 +507,7 @@ def tripadvisor_search_location_details( logger.info(f"Requesting details for location ID {location_id}") try: - response = requests.get(url, headers=headers) + response = requests.get(url, headers=headers, timeout=20) logger.info( f"Received response with status code {response.status_code}", ) @@ -523,4 +535,4 @@ def tripadvisor_search_location_details( return ServiceResponse( status=ServiceExecStatus.ERROR, content={"error": str(e)}, - ) + ) \ No newline at end of file From c30f0a6413c5340f36c12149586871812c9acea5 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:43:10 +0800 Subject: [PATCH 23/24] Update tripadvisor.py --- src/agentscope/service/web/tripadvisor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agentscope/service/web/tripadvisor.py b/src/agentscope/service/web/tripadvisor.py index a47ac2d6f..4a2aab742 100644 --- a/src/agentscope/service/web/tripadvisor.py +++ b/src/agentscope/service/web/tripadvisor.py @@ -535,4 +535,4 @@ def tripadvisor_search_location_details( return ServiceResponse( status=ServiceExecStatus.ERROR, content={"error": str(e)}, - ) \ No newline at end of file + ) From 08d88f30874540d05536815b5bf3509846bd8335 Mon Sep 17 00:00:00 2001 From: zyzhang1130 <36942574+zyzhang1130@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:59:09 +0800 Subject: [PATCH 24/24] Update __init__.py resolve conflict --- src/agentscope/service/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agentscope/service/__init__.py b/src/agentscope/service/__init__.py index 1d2b7cf2c..26b93b5dc 100644 --- a/src/agentscope/service/__init__.py +++ b/src/agentscope/service/__init__.py @@ -106,8 +106,8 @@ def get_help() -> None: "openai_image_to_text", "openai_edit_image", "openai_create_image_variation", - "tripadvisor_search_location_photos", "tripadvisor_search", + "tripadvisor_search_location_photos", "tripadvisor_search_location_details", # to be deprecated "ServiceFactory",