chore: import upstream snapshot with attribution
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:43:05 +08:00
commit 426e9eeabd
41828 changed files with 9656266 additions and 0 deletions
@@ -0,0 +1,833 @@
import datetime
from copy import deepcopy
from typing import Dict, List, Optional, Union
from benchmarks.bfcl.executable_runtime.func_source_code.long_context import (
FILE_CONTENT_EXTENSION,
FILES_TAIL_USED,
POPULATE_FILE_EXTENSION,
)
class File:
def __init__(self, name: str, content: str = "") -> None:
"""
Initialize a file with a name and optional content.
Args:
name (str): The name of the file.
content (str, optional): The initial content of the file. Defaults to an empty string.
"""
self.name: str = name
self.content: str = content
self._last_modified: datetime.datetime = datetime.datetime.now()
def _write(self, new_content: str) -> None:
"""
Write new content to the file and update the last modified time.
Args:
new_content (str): The new content to write to the file.
"""
self.content = new_content
self._last_modified = datetime.datetime.now()
def _read(self) -> str:
"""
Read the content of the file.
Returns:
content (str): The current content of the file.
"""
return self.content
def _append(self, additional_content: str) -> None:
"""
Append content to the existing file content.
Args:
additional_content (str): The content to append to the file.
"""
self.content += additional_content
self._last_modified = datetime.datetime.now()
def __repr__(self):
return f"<<File: {self.name}, Content: {self.content}>>"
def __eq__(self, other: object) -> bool:
if not isinstance(other, File):
return False
return self.name == other.name and self.content == other.content
class Directory:
def __init__(self, name: str, parent: Optional["Directory"] = None) -> None:
"""
Initialize a directory with a name.
Args:
name (str): The name of the directory.
"""
self.name: str = name
self.parent: Optional["Directory"] = parent
self.contents: Dict[str, Union["File", "Directory"]] = {}
def _add_file(self, file_name: str, content: str = "") -> None:
"""
Add a new file to the directory.
Args:
file_name (str): The name of the file.
content (str, optional): The content of the new file. Defaults to an empty string.
"""
if file_name in self.contents:
raise ValueError(
f"File '{file_name}' already exists in directory '{self.name}'."
)
new_file = File(file_name, content)
self.contents[file_name] = new_file
def _add_directory(self, dir_name: str) -> None:
"""
Add a new subdirectory to the directory.
Args:
dir_name (str): The name of the subdirectory.
"""
if dir_name in self.contents:
raise ValueError(
f"Directory '{dir_name}' already exists in directory '{self.name}'."
)
new_dir = Directory(dir_name, self)
self.contents[dir_name] = new_dir
def _get_item(self, item_name: str) -> Union["File", "Directory", None]:
"""
Get an item (file or subdirectory) from the directory.
Args:
item_name (str): The name of the item to retrieve.
Returns:
item (any): The retrieved item or None if it does not exist.
"""
if item_name == ".":
return self
return self.contents.get(item_name)
def _list_contents(self) -> List[str]:
"""
List the names of all contents in the directory.
Returns:
contents (List[str]): A list of names of the files and subdirectories in the directory.
"""
return list(self.contents.keys())
def __repr__(self):
return f"<Directory: {self.name}, Parent: {self.parent.name if self.parent else None}, Contents: {self.contents}>"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Directory):
return False
return self.name == other.name and self.contents == other.contents
DEFAULT_STATE = {"root": Directory("/", None)}
class GorillaFileSystem:
def __init__(self) -> None:
"""
Initialize the Gorilla file system with a root directory
"""
self.root: Directory
self._current_dir: Directory
self._api_description = "This tool belongs to the Gorilla file system. It is a simple file system that allows users to perform basic file operations such as navigating directories, creating files and directories, reading and writing to files, etc."
def __eq__(self, other: object) -> bool:
if not isinstance(other, GorillaFileSystem):
return False
return self.root == other.root
def _load_scenario(self, scenario: dict, long_context: bool = False) -> None:
"""
Load a scenario into the file system.
Args:
scenario (dict): The scenario to load.
The scenario always starts with a root directory. Each directory can contain files or subdirectories.
The key is the name of the file or directory, and the value is a dictionary with the following keys
An example scenario:
Here John is the root directory and it contains a home directory with a user directory inside it.
The user directory contains a file named file1.txt and a directory named directory1.
Root is not a part of the scenario and it's just easy for parsing. During generation, you should have at most 2 layers.
{
"root": {
"john": {
"type": "directory",
"contents": {
"home": {
"type": "directory",
"contents": {
"user": {
"type": "directory",
"contents": {
"file1.txt": {
"type": "file",
"content": "Hello, world!"
},
"directory1": {
"type": "directory",
"contents": {}
}
}
}
}
}
}
}
}
"""
DEFAULT_STATE_COPY = deepcopy(DEFAULT_STATE)
self.long_context = long_context
self.root = DEFAULT_STATE_COPY["root"]
if "root" in scenario:
root_dir = Directory(list(scenario["root"].keys())[0], None)
self.root = self._load_directory(
scenario["root"][list(scenario["root"].keys())[0]]["contents"], root_dir
)
self._current_dir = self.root
def _load_directory(
self, current: dict, parent: Optional[Directory] = None
) -> Directory:
"""
Load a directory and its contents from a dictionary.
Args:
data (dict): The dictionary representing the directory.
parent (Directory, optional): The parent directory. Defaults to None.
Returns:
Directory: The loaded directory.
"""
is_bottommost = True
for dir_name, dir_data in current.items():
if dir_data["type"] == "directory":
is_bottommost = False
new_dir = Directory(dir_name, parent)
new_dir = self._load_directory(dir_data["contents"], new_dir)
parent.contents[dir_name] = new_dir
elif dir_data["type"] == "file":
content = dir_data["content"]
if self.long_context and dir_name not in FILES_TAIL_USED:
content += FILE_CONTENT_EXTENSION
new_file = File(dir_name, content)
parent.contents[dir_name] = new_file
if is_bottommost and self.long_context:
self._populate_directory(parent)
return parent
def _populate_directory(
self, directory: Directory
) -> None: # Used only for long context
"""
Populate an innermost directory with multiple empty files.
Args:
directory (Directory): The innermost directory to populate.
"""
for i in range(len(POPULATE_FILE_EXTENSION)):
name = POPULATE_FILE_EXTENSION[i]
file_name = f"{name}"
directory._add_file(file_name)
def pwd(self):
"""
Return the current working directory path.
Args:
None
Returns:
current_working_directory (str): The current working directory path.
"""
path = []
dir = self._current_dir
while dir is not None and dir.name != self.root:
path.append(dir.name)
dir = dir.parent
return {"current_working_directory": "/" + "/".join(reversed(path))}
def ls(self, a: bool = False) -> Dict[str, List[str]]:
"""
List the contents of the current directory.
Args:
a (bool): [Optional] Show hidden files and directories. Defaults to False.
Returns:
current_directory_content (List[str]): A list of the contents of the specified directory.
"""
contents = self._current_dir._list_contents()
if not a:
contents = [item for item in contents if not item.startswith(".")]
return {"current_directory_content": contents}
def cd(self, folder: str) -> Union[None, Dict[str, str]]:
"""
Change the current working directory to the specified folder.
Args:
folder (str): The folder of the directory to change to. You can only change one folder level at a time.
Returns:
current_working_directory (str): The new current working directory path.
"""
# turn "../" → "..", "/" → ""
folder = folder.rstrip("/")
if folder == "":
folder = "/"
# Check if path is nested
if folder not in {".", "..", "/"} and "/" in folder:
return {
"error": f"cd: {folder}: Unsupported path. Only one folder level at a time is supported."
}
# Handle navigating to the parent directory with "cd .."
if folder == "..":
if self._current_dir.parent:
self._current_dir = self._current_dir.parent
elif self.root == self._current_dir:
return {"error": "Current directory is already the root. Cannot go back."}
else:
return {"error": "cd: ..: No such directory"}
return {}
# Handle absolute or relative paths
target_dir = self._navigate_to_directory(folder)
if isinstance(target_dir, dict): # This means there was an error from _navigate_to_directory
return target_dir
self._current_dir = target_dir
return {"current_working_directory": target_dir.name}
def _validate_file_or_directory_name(self, dir_name: str) -> bool:
if any(c in dir_name for c in '|/\\?%*:"><'):
return False
return True
def mkdir(self, dir_name: str) -> Union[None, Dict[str, str]]:
"""
Create a new directory in the current directory.
Args:
dir_name (str): The name of the new directory at current directory. You can only create directory at current directory.
"""
if not self._validate_file_or_directory_name(dir_name):
return {
"error": f"mkdir: cannot create directory '{dir_name}': Invalid character"
}
if dir_name in self._current_dir.contents:
return {"error": f"mkdir: cannot create directory '{dir_name}': File exists"}
self._current_dir._add_directory(dir_name)
return None
def touch(self, file_name: str) -> Union[None, Dict[str, str]]:
"""
Create a new file of any extension in the current directory.
Args:
file_name (str): The name of the new file in the current directory. file_name is local to the current directory and does not allow path.
"""
if not self._validate_file_or_directory_name(file_name):
return {"error": f"touch: cannot touch '{file_name}': Invalid character"}
if file_name in self._current_dir.contents:
return {"error": f"touch: cannot touch '{file_name}': File exists"}
self._current_dir._add_file(file_name)
return None
def echo(
self, content: str, file_name: Optional[str] = None
) -> Union[Dict[str, str], None]:
"""
Write content to a file at current directory or display it in the terminal.
Args:
content (str): The content to write or display.
file_name (str): [Optional] The name of the file at current directory to write the content to. Defaults to None.
Returns:
terminal_output (str): The content if no file name is provided, or None if written to file.
"""
if file_name is None:
return {"terminal_output": content}
if not self._validate_file_or_directory_name(file_name):
return {"error": f"echo: cannot write to '{file_name}': Invalid character"}
if file_name:
if file_name in self._current_dir.contents:
self._current_dir._get_item(file_name)._write(content)
else:
return {"error": f"echo: cannot write to '{file_name}': No such file"}
else:
return {"terminal_output": content}
def cat(self, file_name: str) -> Dict[str, str]:
"""
Display the contents of a file of any extension from currrent directory.
Args:
file_name (str): The name of the file from current directory to display. No path is allowed.
Returns:
file_content (str): The content of the file.
"""
if not self._validate_file_or_directory_name(file_name):
return {"error": f"cat: '{file_name}': Invalid character"}
if file_name in self._current_dir.contents:
item = self._current_dir._get_item(file_name)
if isinstance(item, File):
return {"file_content": item._read()}
else:
return {"error": f"cat: '{file_name}': Is a directory"}
else:
return {"error": f"cat: '{file_name}': No such file or directory"}
def find(self, path: str = ".", name: Optional[str] = None) -> Dict[str, List[str]]:
"""
Find any file or directories under specific path that contain name in its file name.
This method searches for files of any extension and directories within a specified path that match
the given name. If no name is provided, it returns all files and directories
in the specified path and its subdirectories.
Note: This method performs a recursive search through all subdirectories of the given path.
Args:
path (str): The directory path to start the search. Defaults to the current directory (".").
name (str): [Optional] The name of the file or directory to search for. If None, all items are returned.
Returns:
matches (List[str]): A list of matching file and directory paths relative to the given path.
"""
matches = []
# Navigate to the requested path first
target_dir = self._navigate_to_directory(path)
if isinstance(target_dir, dict): # invalid path
# Replace the tool name in the error message for clarity
original_msg = target_dir.get("error", "")
# e.g. "cd: '/foo': No such file or directory" -> "find: '/foo': No such file or directory"
if original_msg.startswith("cd:"):
return {"error": original_msg.replace("cd:", "find:", 1)}
return target_dir
def recursive_search(directory: Directory, base_path: str) -> None:
for item_name, item in directory.contents.items():
item_path = f"{base_path}/{item_name}"
if name is None or name in item_name:
matches.append(item_path)
if isinstance(item, Directory):
recursive_search(item, item_path)
recursive_search(target_dir, path.rstrip("/"))
return {"matches": matches}
def wc(self, file_name: str, mode: str = "l") -> Dict[str, Union[int, str]]:
"""
Count the number of lines, words, and characters in a file of any extension from current directory.
Args:
file_name (str): Name of the file of current directory to perform wc operation on.
mode (str): Mode of operation ('l' for lines, 'w' for words, 'c' for characters).
Returns:
count (int): The count of the number of lines, words, or characters in the file.
type (str): The type of unit we are counting. [Enum]: ["lines", "words", "characters"]
"""
if mode not in ["l", "w", "c"]:
return {"error": f"wc: invalid mode '{mode}'"}
if file_name in self._current_dir.contents:
file = self._current_dir._get_item(file_name)
if isinstance(file, File):
content = file._read()
if mode == "l":
line_count = len(content.splitlines())
return {"count": line_count, "type": "lines"}
elif mode == "w":
word_count = len(content.split())
return {"count": word_count, "type": "words"}
elif mode == "c":
char_count = len(content)
return {"count": char_count, "type": "characters"}
return {"error": f"wc: {file_name}: No such file or directory"}
def sort(self, file_name: str) -> Dict[str, str]:
"""
Sort the contents of a file line by line.
Args:
file_name (str): The name of the file appeared at current directory to sort.
Returns:
sorted_content (str): The sorted content of the file.
"""
if file_name in self._current_dir.contents:
file = self._current_dir._get_item(file_name)
if isinstance(file, File):
content = file._read()
sorted_content = "\n".join(sorted(content.splitlines()))
return {"sorted_content": sorted_content}
return {"error": f"sort: {file_name}: No such file or directory"}
def grep(self, file_name: str, pattern: str) -> Dict[str, List[str]]:
"""
Search for lines in a file of any extension at current directory that contain the specified pattern.
Args:
file_name (str): The name of the file to search. No path is allowed and you can only perform on file at local directory.
pattern (str): The pattern to search for.
Returns:
matching_lines (List[str]): Lines that match the pattern.
"""
if file_name in self._current_dir.contents:
file = self._current_dir._get_item(file_name)
if isinstance(file, File):
content = file._read()
matching_lines = [line for line in content.splitlines() if pattern in line]
return {"matching_lines": matching_lines}
return {"error": f"grep: {file_name}: No such file or directory"}
def du(self, human_readable: bool = False) -> Dict[str, str]:
"""
Estimate the disk usage of a directory and its contents.
Args:
human_readable (bool): If True, returns the size in human-readable format (e.g., KB, MB).
Returns:
disk_usage (str): The estimated disk usage.
"""
def get_size(item: Union[File, Directory]) -> int:
if isinstance(item, File):
return len(item._read().encode("utf-8"))
elif isinstance(item, Directory):
return sum(get_size(child) for child in item.contents.values())
return 0
target_dir = self._navigate_to_directory(None)
if isinstance(target_dir, dict): # Error condition check
return target_dir
total_size = get_size(target_dir)
if human_readable:
for unit in ["B", "KB", "MB", "GB", "TB"]:
if total_size < 1024:
size_str = f"{total_size:.2f} {unit}"
break
total_size /= 1024
else:
size_str = f"{total_size:.2f} PB"
else:
size_str = f"{total_size} bytes"
return {"disk_usage": size_str}
def tail(self, file_name: str, lines: int = 10) -> Dict[str, str]:
"""
Display the last part of a file of any extension.
Args:
file_name (str): The name of the file to display. No path is allowed and you can only perform on file at local directory.
lines (int): The number of lines to display from the end of the file. Defaults to 10.
Returns:
last_lines (str): The last part of the file.
"""
if file_name in self._current_dir.contents:
file = self._current_dir._get_item(file_name)
if isinstance(file, File):
content = file._read().splitlines()
if lines > len(content):
lines = len(content)
last_lines = content[-lines:]
return {"last_lines": "\n".join(last_lines)}
return {"error": f"tail: {file_name}: No such file or directory"}
def diff(self, file_name1: str, file_name2: str) -> Dict[str, str]:
"""
Compare two files of any extension line by line at the current directory.
Args:
file_name1 (str): The name of the first file in current directory.
file_name2 (str): The name of the second file in current directory.
Returns:
diff_lines (str): The differences between the two files.
"""
if (
file_name1 in self._current_dir.contents
and file_name2 in self._current_dir.contents
):
file1 = self._current_dir._get_item(file_name1)
file2 = self._current_dir._get_item(file_name2)
if isinstance(file1, File) and isinstance(file2, File):
content1 = file1._read().splitlines()
content2 = file2._read().splitlines()
diff_lines = [
f"- {line1}\n+ {line2}"
for line1, line2 in zip(content1, content2)
if line1 != line2
]
return {"diff_lines": "\n".join(diff_lines)}
return {"error": f"diff: {file_name1} or {file_name2}: No such file or directory"}
def mv(self, source: str, destination: str) -> Dict[str, str]:
"""
Move a file or directory from one location to another.
Args:
source (str): Source name of the file or directory to move. Source must be local to the current directory.
destination (str): The destination name to move the file or directory to. Destination must be local to the current directory and cannot be a path. If destination is not an existing directory like when renaming something, destination is the new file name.
Returns:
result (str): The result of the move operation.
"""
if source not in self._current_dir.contents:
return {"error": f"mv: cannot move '{source}': No such file or directory"}
item = self._current_dir._get_item(source)
if not isinstance(item, (File, Directory)):
return {"error": f"mv: cannot move '{source}': Not a file or directory"}
if "/" in destination:
return {
"error": "mv: path not allowed in destination. Provide only a file or directory name."
}
# Check if the destination is an existing directory
if destination in self._current_dir.contents:
dest_item = self._current_dir._get_item(destination)
if isinstance(dest_item, Directory):
# Move source into the destination directory
new_destination = f"{source}"
if new_destination in dest_item.contents:
return {
"error": f"mv: cannot move '{source}' to '{destination}/{source}': File exists"
}
else:
self._current_dir.contents.pop(source)
if isinstance(item, File):
dest_item._add_file(source, item.content)
else:
dest_item._add_directory(source)
dest_item.contents[source].contents = item.contents
return {"result": f"'{source}' moved to '{destination}/{source}'"}
else:
return {
"error": f"mv: cannot move '{source}' to '{destination}': Not a directory"
}
else:
# Destination is not an existing directory, move/rename the item
self._current_dir.contents.pop(source)
if isinstance(item, File):
self._current_dir._add_file(destination, item.content)
else:
self._current_dir._add_directory(destination)
self._current_dir.contents[destination].contents = item.contents
return {"result": f"'{source}' moved to '{destination}'"}
def rm(self, file_name: str) -> Dict[str, str]:
"""
Remove a file or directory.
Args:
file_name (str): The name of the file or directory to remove.
Returns:
result (str): The result of the remove operation.
"""
if file_name in self._current_dir.contents:
item = self._current_dir._get_item(file_name)
if isinstance(item, File) or isinstance(item, Directory):
self._current_dir.contents.pop(file_name)
return {"result": f"'{file_name}' removed"}
else:
return {
"error": f"rm: cannot remove '{file_name}': Not a file or directory"
}
else:
return {"error": f"rm: cannot remove '{file_name}': No such file or directory"}
def rmdir(self, dir_name: str) -> Dict[str, str]:
"""
Remove a directory at current directory.
Args:
dir_name (str): The name of the directory to remove. Directory must be local to the current directory.
Returns:
result (str): The result of the remove operation.
"""
if dir_name in self._current_dir.contents:
item = self._current_dir._get_item(dir_name)
if isinstance(item, Directory):
if item.contents: # Check if directory is not empty
return {
"error": f"rmdir: cannot remove '{dir_name}': Directory not empty"
}
else:
self._current_dir.contents.pop(dir_name)
return {"result": f"'{dir_name}' removed"}
else:
return {"error": f"rmdir: cannot remove '{dir_name}': Not a directory"}
else:
return {
"error": f"rmdir: cannot remove '{dir_name}': No such file or directory"
}
def cp(self, source: str, destination: str) -> Dict[str, str]:
"""
Copy a file or directory from one location to another.
If the destination is a directory, the source file or directory will be copied
into the destination directory.
Both source and destination must be local to the current directory.
Args:
source (str): The name of the file or directory to copy.
destination (str): The destination name to copy the file or directory to.
If the destination is a directory, the source will be copied
into this directory. No file paths allowed.
Returns:
result (str): The result of the copy operation or an error message if the operation fails.
"""
if source not in self._current_dir.contents:
return {"error": f"cp: cannot copy '{source}': No such file or directory"}
item = self._current_dir._get_item(source)
if not isinstance(item, (File, Directory)):
return {"error": f"cp: cannot copy '{source}': Not a file or directory"}
if "/" in destination:
return {
"error": "cp: path not allowed in destination. Provide only a file or directory name."
}
# Check if the destination is an existing directory
if destination in self._current_dir.contents:
dest_item = self._current_dir._get_item(destination)
if isinstance(dest_item, Directory):
# Copy source into the destination directory
new_destination = f"{destination}/{source}"
if new_destination in dest_item.contents:
return {
"error": f"cp: cannot copy '{source}' to '{destination}/{source}': File exists"
}
else:
if isinstance(item, File):
dest_item._add_file(source, item.content)
else:
dest_item._add_directory(source)
dest_item.contents[source].contents = item.contents.copy()
return {"result": f"'{source}' copied to '{destination}/{source}'"}
else:
return {
"error": f"cp: cannot copy '{source}' to '{destination}': Not a directory"
}
else:
# Destination is not an existing directory, perform the copy
if isinstance(item, File):
self._current_dir._add_file(destination, item.content)
else:
self._current_dir._add_directory(destination)
self._current_dir.contents[destination].contents = item.contents.copy()
return {"result": f"'{source}' copied to '{destination}'"}
def _navigate_to_directory(
self, path: Optional[str]
) -> Union[Directory, Dict[str, str]]:
"""
Navigate to a specified directory path from the current directory.
Args:
path (str): [Optional] The path to navigate to. Defaults to None (current directory).
Returns:
target_directory (Directory or dict): The target directory object or error message.
"""
if path is None or path == ".":
return self._current_dir
elif path == "/":
return self.root
dirs = path.strip("/").split("/")
temp_dir = self._current_dir if not path.startswith("/") else self.root
for dir_name in dirs:
next_dir = temp_dir._get_item(dir_name)
if isinstance(next_dir, Directory):
temp_dir = next_dir
else:
return {"error": f"cd: '{path}': No such file or directory"}
return temp_dir
def _parse_positions(self, positions: str) -> List[int]:
"""
Helper function to parse position strings, e.g., '1,3,5', '1-5', '-3', or '3-'.
Args:
positions (str): The position string to parse.
Returns:
list (List[int]): A list of integers representing the positions.
"""
result = []
if "," in positions:
for part in positions.split(","):
result.extend(self._parse_positions(part))
elif "-" in positions:
start, end = positions.split("-")
start = int(start) if start else 1
end = int(end) if end else float("inf")
result.extend(range(start, end + 1))
else:
result.append(int(positions))
return result
File diff suppressed because one or more lines are too long
@@ -0,0 +1,373 @@
import math
from decimal import Decimal, InvalidOperation, getcontext
from typing import Dict, List
import mpmath
class MathAPI:
def __init__(self):
self._api_description = "This tool belongs to the Math API, which provides various mathematical operations."
def logarithm(
self, value: float, base: float, precision: int
) -> Dict[str, float]:
"""
Compute the logarithm of a number with adjustable precision using mpmath.
Args:
value (float): The number to compute the logarithm of.
base (float): The base of the logarithm.
precision (int): Desired precision for the result.
Returns:
result (float): The logarithm of the number with respect to the given base.
"""
try:
# Set precision for mpmath
mpmath.mp.dps = precision
# Use mpmath for high-precision logarithmic calculations
result = mpmath.log(value) / mpmath.log(base)
return {"result": result}
except Exception as e:
return {"error": str(e)}
def mean(self, numbers: List[float]) -> Dict[str, float]:
"""
Calculate the mean of a list of numbers.
Args:
numbers (List[float]): List of numbers to calculate the mean of.
Returns:
result (float): Mean of the numbers.
"""
if not numbers:
return {"error": "Cannot calculate mean of an empty list"}
try:
return {"result": sum(numbers) / len(numbers)}
except TypeError:
return {"error": "All elements in the list must be numbers"}
def standard_deviation(self, numbers: List[float]) -> Dict[str, float]:
"""
Calculate the standard deviation of a list of numbers.
Args:
numbers (List[float]): List of numbers to calculate the standard deviation of.
Returns:
result (float): Standard deviation of the numbers.
"""
if not numbers:
return {"error": "Cannot calculate standard deviation of an empty list"}
try:
mean = sum(numbers) / len(numbers)
variance = sum((x - mean) ** 2 for x in numbers) / len(numbers)
return {"result": math.sqrt(variance)}
except TypeError:
return {"error": "All elements in the list must be numbers"}
def si_unit_conversion(
self, value: float, unit_in: str, unit_out: str
) -> Dict[str, float]:
"""
Convert a value from one SI unit to another.
Args:
value (float): Value to be converted.
unit_in (str): Unit of the input value.
unit_out (str): Unit to convert the value to.
Returns:
result (float): Converted value in the new unit.
"""
to_meters = {"km": 1000, "m": 1, "cm": 0.01, "mm": 0.001, "um": 1e-6, "nm": 1e-9}
from_meters = {unit: 1 / factor for unit, factor in to_meters.items()}
if not isinstance(value, (int, float)):
return {"error": "Value must be a number"}
if unit_in not in to_meters or unit_out not in from_meters:
return {
"error": f"Conversion from '{unit_in}' to '{unit_out}' is not supported"
}
try:
value_in_meters = value * to_meters[unit_in]
result = value_in_meters * from_meters[unit_out]
return {"result": result}
except OverflowError:
return {"error": "Conversion resulted in a value too large to represent"}
def imperial_si_conversion(
self, value: float, unit_in: str, unit_out: str
) -> Dict[str, float]:
"""
Convert a value between imperial and SI units.
Args:
value (float): Value to be converted.
unit_in (str): Unit of the input value.
unit_out (str): Unit to convert the value to.
Returns:
result (float): Converted value in the new unit.
"""
conversion = {
"cm_to_in": 0.393701,
"in_to_cm": 2.54,
"m_to_ft": 3.28084,
"ft_to_m": 0.3048,
"m_to_yd": 1.09361,
"yd_to_m": 0.9144,
"km_to_miles": 0.621371,
"miles_to_km": 1.60934,
"kg_to_lb": 2.20462,
"lb_to_kg": 0.453592,
"celsius_to_fahrenheit": 1.8,
"fahrenheit_to_celsius": 5 / 9,
}
if not isinstance(value, (int, float)):
return {"error": "Value must be a number"}
if unit_in == unit_out:
return {"result": value}
conversion_key = f"{unit_in}_to_{unit_out}"
if conversion_key not in conversion:
return {
"error": f"Conversion from '{unit_in}' to '{unit_out}' is not supported"
}
try:
if unit_in == "celsius" and unit_out == "fahrenheit":
result = (value * conversion[conversion_key]) + 32
elif unit_in == "fahrenheit" and unit_out == "celsius":
result = (value - 32) * conversion[conversion_key]
else:
result = value * conversion[conversion_key]
return {"result": result}
except OverflowError:
return {"error": "Conversion resulted in a value too large to represent"}
def add(self, a: float, b: float) -> Dict[str, float]:
"""
Add two numbers.
Args:
a (float): First number.
b (float): Second number.
Returns:
result (float): Sum of the two numbers.
"""
try:
return {"result": a + b}
except TypeError:
return {"error": "Both inputs must be numbers"}
def subtract(self, a: float, b: float) -> Dict[str, float]:
"""
Subtract one number from another.
Args:
a (float): Number to subtract from.
b (float): Number to subtract.
Returns:
result (float): Difference between the two numbers.
"""
try:
return {"result": a - b}
except TypeError:
return {"error": "Both inputs must be numbers"}
def multiply(self, a: float, b: float) -> Dict[str, float]:
"""
Multiply two numbers.
Args:
a (float): First number.
b (float): Second number.
Returns:
result (float): Product of the two numbers.
"""
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
return {"error": "Both inputs must be numbers"}
try:
return {"result": a * b}
except TypeError:
return {"error": "Both inputs must be numbers"}
def divide(self, a: float, b: float) -> Dict[str, float]:
"""
Divide one number by another.
Args:
a (float): Numerator.
b (float): Denominator.
Returns:
result (float): Quotient of the division.
"""
try:
if b == 0:
return {"error": "Cannot divide by zero"}
return {"result": a / b}
except TypeError:
return {"error": "Both inputs must be numbers"}
def power(self, base: float, exponent: float) -> Dict[str, float]:
"""
Raise a number to a power.
Args:
base (float): The base number.
exponent (float): The exponent.
Returns:
result (float): The base raised to the power of the exponent.
"""
try:
return {"result": base**exponent}
except TypeError:
return {"error": "Both inputs must be numbers"}
def square_root(self, number: float, precision: int) -> Dict[str, float]:
"""
Calculate the square root of a number with adjustable precision using the decimal module.
Args:
number (float): The number to calculate the square root of.
precision (int): Desired precision for the result.
Returns:
result (float): The square root of the number, or an error message.
"""
try:
if number < 0:
return {"error": "Cannot calculate square root of a negative number"}
# Set the precision for the decimal context
getcontext().prec = precision
# Use Decimal for high-precision square root calculation
decimal_number = Decimal(number)
result = decimal_number.sqrt()
return {"result": result}
except (TypeError, InvalidOperation):
return {
"error": "Input must be a number or computation resulted in an invalid operation"
}
def absolute_value(self, number: float) -> Dict[str, float]:
"""
Calculate the absolute value of a number.
Args:
number (float): The number to calculate the absolute value of.
Returns:
result (float): The absolute value of the number.
"""
try:
return {"result": abs(number)}
except TypeError:
return {"error": "Input must be a number"}
def round_number(
self, number: float, decimal_places: int = 0
) -> Dict[str, float]:
"""
Round a number to a specified number of decimal places.
Args:
number (float): The number to round.
decimal_places (int): [Optional] The number of decimal places to round to. Defaults to 0.
Returns:
result (float): The rounded number.
"""
try:
return {"result": round(number, decimal_places)}
except TypeError:
return {
"error": "First input must be a number, second input must be an integer"
}
def percentage(self, part: float, whole: float) -> Dict[str, float]:
"""
Calculate the percentage of a part relative to a whole.
Args:
part (float): The part value.
whole (float): The whole value.
Returns:
result (float): The percentage of the part relative to the whole.
"""
try:
if whole == 0:
return {"error": "Whole value cannot be zero"}
return {"result": (part / whole) * 100}
except TypeError:
return {"error": "Both inputs must be numbers"}
def min_value(self, numbers: List[float]) -> Dict[str, float]:
"""
Find the minimum value in a list of numbers.
Args:
numbers (List[float]): List of numbers to find the minimum from.
Returns:
result (float): The minimum value in the list.
"""
if not numbers:
return {"error": "Cannot find minimum of an empty list"}
try:
return {"result": min(numbers)}
except TypeError:
return {"error": "All elements in the list must be numbers"}
def max_value(self, numbers: List[float]) -> Dict[str, float]:
"""
Find the maximum value in a list of numbers.
Args:
numbers (List[float]): List of numbers to find the maximum from.
Returns:
result (float): The maximum value in the list.
"""
if not numbers:
return {"error": "Cannot find maximum of an empty list"}
try:
return {"result": max(numbers)}
except TypeError:
return {"error": "All elements in the list must be numbers"}
def sum_values(self, numbers: List[float]) -> Dict[str, float]:
"""
Calculate the sum of a list of numbers.
Args:
numbers (List[float]): List of numbers to sum.
Returns:
result (float): The sum of all numbers in the list.
"""
if not numbers:
return {"error": "Cannot calculate sum of an empty list"}
try:
return {"result": sum(numbers)}
except TypeError:
return {"error": "All elements in the list must be numbers"}
@@ -0,0 +1,114 @@
import json
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional
try:
from overrides import final
except ImportError: # pragma: no cover - optional upstream dependency
from typing import TypeVar
_F = TypeVar("_F")
def final(func: _F) -> _F:
return func
# Upstream imports replaced with local shims. Memory categories require
# additional upstream scaffolding (snapshot dirs, memory_prereq_conversation
# files) that we don't vendor — they are marked SKIPPED_UNSUPPORTED by the
# runner. These shims keep the module importable for non-memory tests.
try:
from bfcl_eval.utils import ( # type: ignore[import-not-found]
get_directory_structure_by_id,
is_first_memory_prereq_entry,
is_memory_prereq,
)
except ImportError:
def get_directory_structure_by_id(test_id): # type: ignore[no-redef]
# Upstream places snapshots under a scenario/test-id-derived folder.
# For local smoke tests and vendored lightweight runtime use, a stable
# sanitized id is enough to preserve per-test isolation.
safe = str(test_id).replace("/", "_").replace("..", "_")
return safe or "memory_unit_test"
def is_first_memory_prereq_entry(test_id): # type: ignore[no-redef]
return False
def is_memory_prereq(test_id): # type: ignore[no-redef]
return False
class MemoryAPI(ABC):
"""
A class that provides APIs to manage short-term and long-term memory data in a key-value format.
"""
def __init__(self):
self.core_memory = {}
self.archival_memory = {}
self.snapshot_folder: Path | None = None
@final
def _prepare_snapshot(self, initial_config: dict) -> Optional[dict]:
"""Helper to prepare snapshot folders/files and load previous memory data.
Sub-classes should call this method and then load the specific portions of the snapshot
they are interested in (e.g. `core_memory` or `archival_memory`) into their own in-memory
representation.
Args:
initial_config (dict): The configuration dict passed from the evaluation harness.
Returns:
Optional[dict]: The previously saved memory snapshot if it exists, otherwise `None`.
"""
# We don't care about the ``long_context`` parameter here subclasses keep that
model_result_dir: Path = initial_config["model_result_dir"]
self.test_id: str = initial_config["test_id"]
self.scenario: str = initial_config["scenario"]
memory_snapshot_folder = (
model_result_dir
/ get_directory_structure_by_id(self.test_id)
/ "memory_snapshot"
)
# Keep prerequisite checkpoints in a dedicated sub-folder so that multiple prerequisite
# entries for the same scenario do not overwrite each other.
if is_memory_prereq(self.test_id):
self.snapshot_folder = memory_snapshot_folder / "prereq_checkpoints"
else:
self.snapshot_folder = memory_snapshot_folder
self.snapshot_folder.mkdir(parents=True, exist_ok=True)
self.latest_snapshot_file = memory_snapshot_folder / f"{self.scenario}_final.json"
if is_first_memory_prereq_entry(self.test_id):
# The very first entry of a prerequisite chain should start with a clean state.
return None
# For non-first entries we MUST have a snapshot to load from.
# But if the first entry got a error during inference, then there will be no snapshot file
if not self.latest_snapshot_file.exists():
msg = (
"⚠️" * 100
+ f"\nWarning: Not first memory entry, but no snapshot file found in this path: {self.latest_snapshot_file}. The memory will start empty for {initial_config['test_id']}.\n"
+ "⚠️" * 100
)
print(msg)
return None
with open(self.latest_snapshot_file, "r") as f:
return json.load(f)
@abstractmethod
def _load_scenario(self, initial_config: dict, long_context: bool = False):
pass
@abstractmethod
def _flush_memory_to_local_file(self):
pass
@abstractmethod
def _dump_core_memory_to_context(self) -> str:
pass
@@ -0,0 +1,343 @@
import json
import re
from copy import deepcopy
from typing import Dict, List, Tuple
from benchmarks.bfcl.executable_runtime.func_source_code.memory_api_metaclass import (
MemoryAPI,
)
try:
from rank_bm25 import BM25Plus
except ImportError: # pragma: no cover - lightweight fallback for CI smoke
class BM25Plus: # type: ignore[no-redef]
def __init__(self, tokenized_corpus):
self.tokenized_corpus = [set(tokens) for tokens in tokenized_corpus]
def get_scores(self, tokenized_query):
query = set(tokenized_query)
return [float(len(query & doc)) for doc in self.tokenized_corpus]
# https://lilianweng.github.io/posts/2023-06-23-agent/#component-two-memory
MAX_CORE_MEMORY_SIZE = 7
MAX_CORE_MEMORY_ENTRY_LENGTH = 300
MAX_ARCHIVAL_MEMORY_SIZE = 50
MAX_ARCHIVAL_MEMORY_ENTRY_LENGTH = 2000
class MemoryAPI_kv(MemoryAPI):
"""
A class that provides APIs to manage short-term and long-term memory data in a key-value format.
"""
def __init__(self):
self.core_memory = {}
self.archival_memory = {}
self._api_description = """This tool belongs to the memory suite, which provides APIs to interact with a key-value based memory system."""
self.snapshot_folder = None
def _load_scenario(self, initial_config: dict, long_context: bool = False):
# Set up paths & load snapshots
memory_data = self._prepare_snapshot(initial_config)
# Populate in-memory structures if we have a previous snapshot
if memory_data:
self.core_memory = deepcopy(memory_data["core_memory"])
self.archival_memory = deepcopy(memory_data["archival_memory"])
def _flush_memory_to_local_file(self):
"""
Flush (save) current memory (both core and archival) to a local JSON file.
"""
# Write the snapshot file for the current test entry
with open(self.snapshot_folder / f"{self.test_id}.json", "w") as f:
json.dump(
{
"core_memory": self.core_memory,
"archival_memory": self.archival_memory,
},
f,
indent=4,
)
# Update the latest snapshot file content
with open(self.latest_snapshot_file, "w") as f:
json.dump(
{
"core_memory": self.core_memory,
"archival_memory": self.archival_memory,
},
f,
indent=4,
)
def _dump_core_memory_to_context(self) -> str:
if not self.core_memory:
return "There is no content in the core memory at this point."
return json.dumps(self.core_memory, indent=4)
@staticmethod
def _similarity_search(query: str, corpus: list[str], k: int = 5):
"""
Search for the most similar text in the corpus to the query using BM25+ algorithm.
Args:
query (str): The query text to search for.
corpus (list[str]): A list of text strings to search in.
k (int): The number of results to return.
Returns:
ranked_results (list[tuple[float, str]]): A list of tuples containing the BM25+ score and the text string.
"""
tokenized_corpus = [text.replace("_", " ").lower().split() for text in corpus]
bm25 = BM25Plus(tokenized_corpus)
tokenized_query = query.replace("_", " ").lower().split()
scores = bm25.get_scores(tokenized_query)
ranked_results = sorted(zip(scores, corpus), key=lambda x: x[0], reverse=True)
return {"ranked_results": ranked_results[:k]}
@staticmethod
def _is_valid_key_format(s):
"""
Check if the key is in snake_case format and does not contain spaces.
"""
pattern = r"^[a-z]+(_[a-z0-9]+)*$"
return bool(re.match(pattern, s))
def core_memory_add(self, key: str, value: str) -> Dict[str, str]:
"""
Add a key-value pair to the short-term memory. Make sure to use meaningful keys for easy retrieval later.
Args:
key (str): The key under which the value is stored. The key should be unique and case-sensitive. Keys must be snake_case and cannot contain spaces.
value (str): The value to store in the short-term memory.
Returns:
status (str): Status of the operation.
"""
key, value = str(key), str(value)
if len(self.core_memory) >= MAX_CORE_MEMORY_SIZE:
return {"error": "Core memory is full. Please clear some entries."}
if len(value) > MAX_CORE_MEMORY_ENTRY_LENGTH:
return {
"error": f"Entry is too long. Please shorten the entry to less than {MAX_CORE_MEMORY_ENTRY_LENGTH} characters."
}
if not self._is_valid_key_format(key):
return {"error": "Key must be in snake_case format and cannot contain spaces."}
if key in self.core_memory:
return {"error": "Key name must be unique."}
self.core_memory[key] = value
return {"status": "Key-value pair added."}
def core_memory_remove(self, key: str) -> Dict[str, str]:
"""
Remove a key-value pair from the short-term memory.
Args:
key (str): The key to remove from the short-term memory. Case-sensitive.
Returns:
status (str): Status of the operation.
"""
if key in self.core_memory:
del self.core_memory[key]
return {"status": "Key removed."}
else:
return {"error": "Key not found."}
def core_memory_replace(self, key: str, value: str) -> Dict[str, str]:
"""
Replace a key-value pair in the short-term memory with a new value.
Args:
key (str): The key to replace in the short-term memory. Case-sensitive.
value (str): The new value associated with the key.
Returns:
status (str): Status of the operation.
"""
key, value = str(key), str(value)
if key not in self.core_memory:
return {"error": "Key not found."}
if len(value) > MAX_CORE_MEMORY_ENTRY_LENGTH:
return {
"error": f"Entry is too long. Please shorten the entry to less than {MAX_CORE_MEMORY_ENTRY_LENGTH} characters."
}
self.core_memory[key] = value
return {"status": "Key replaced."}
def core_memory_clear(self) -> Dict[str, str]:
"""
Clear all key-value pairs from the short-term memory, including those from previous interactions. This operation is irreversible.
Returns:
status (str): Status of the operation.
"""
self.core_memory = {}
return {"status": "Short term memory cleared."}
def core_memory_retrieve(self, key: str) -> Dict[str, str]:
"""
Retrieve the value associated with a key from the short-term memory. This function does not support partial key matching or similarity search.
Args:
key (str): The key to retrieve. Case-sensitive. The key must match exactly with the key stored in the memory.
Returns:
value (str): The value associated with the key.
"""
if key not in self.core_memory:
return {"error": "Key not found."}
return {"value": self.core_memory[key]}
def core_memory_list_keys(self) -> Dict[str, List[str]]:
"""
List all keys currently in the short-term memory.
Returns:
keys (List[str]): A list of all keys in the short-term memory.
"""
return {"keys": list(self.core_memory.keys())}
def core_memory_key_search(
self, query: str, k: int = 5
) -> Dict[str, List[Tuple[float, str]]]:
"""
Search for key names in the short-term memory that are similar to the query using BM25+ algorithm.
Args:
query (str): The query text to search for.
k (int): [Optional] The number of results to return.
Returns:
ranked_results (List[Tuple[float, str]]): A list of tuples containing the BM25+ score and the key.
"""
keys = deepcopy(list(self.core_memory.keys()))
return self._similarity_search(query, keys, k)
def core_memory_retrieve_all(self) -> Dict[str, str]:
"""
Retrieve all key-value pairs from the short-term memory.
Returns:
key (str): Each key in the short-term memory.
value (str): The value associated with each key.
"""
return self.core_memory
def archival_memory_add(self, key: str, value: str) -> Dict[str, str]:
"""
Add a key-value pair to the long-term memory. Make sure to use meaningful keys for easy retrieval later.
Args:
key (str): The key under which the value is stored. The key should be unique and case-sensitive. Keys must be snake_case and cannot contain spaces.
value (str): The value to store in the long-term memory.
Returns:
status (str): Status of the operation.
"""
key, value = str(key), str(value)
if len(self.archival_memory) >= MAX_ARCHIVAL_MEMORY_SIZE:
return {"error": "Long term memory is full. Please clear some entries."}
if len(value) > MAX_ARCHIVAL_MEMORY_ENTRY_LENGTH:
return {
"error": f"Entry is too long. Please shorten the entry to less than {MAX_ARCHIVAL_MEMORY_ENTRY_LENGTH} characters."
}
if not self._is_valid_key_format(key):
return {"error": "Key must be in snake_case format and cannot contain spaces."}
if key in self.archival_memory:
return {"error": "Key name must be unique."}
self.archival_memory[key] = value
return {"status": "Key added."}
def archival_memory_remove(self, key: str) -> Dict[str, str]:
"""
Remove a key-value pair from the long-term memory.
Args:
key (str): The key to remove from the long-term memory. Case-sensitive.
Returns:
status (str): Status of the operation.
"""
if key in self.archival_memory:
del self.archival_memory[key]
return {"status": "Key removed."}
else:
return {"error": "Key not found."}
def archival_memory_replace(self, key: str, value: str) -> Dict[str, str]:
"""
Replace a key-value pair in the long-term memory with a new value.
Args:
key (str): The key to replace in the long-term memory. Case-sensitive.
value (str): The new value associated with the key.
Returns:
status (str): Status of the operation.
"""
key, value = str(key), str(value)
if key not in self.archival_memory:
return {"error": "Key not found."}
if len(value) > MAX_ARCHIVAL_MEMORY_ENTRY_LENGTH:
return {
"error": f"Entry is too long. Please shorten the entry to less than {MAX_ARCHIVAL_MEMORY_ENTRY_LENGTH} characters."
}
self.archival_memory[key] = value
return {"status": "Key replaced."}
def archival_memory_clear(self) -> Dict[str, str]:
"""
Clear all key-value pairs from the long-term memory, including those from previous interactions. This operation is irreversible.
Returns:
status (str): Status of the operation.
"""
self.archival_memory = {}
return {"status": "Long term memory cleared."}
def archival_memory_retrieve(self, key: str) -> Dict[str, str]:
"""
Retrieve the value associated with a key from the long-term memory. This function does not support partial key matching or similarity search.
Args:
key (str): The key to retrieve. Case-sensitive. The key must match exactly with the key stored in the memory.
Returns:
value (str): The value associated with the key.
"""
if key not in self.archival_memory:
return {"error": "Key not found."}
return {"value": self.archival_memory[key]}
def archival_memory_list_keys(self) -> Dict[str, List[str]]:
"""
List all keys currently in the long-term memory.
Returns:
keys (List[str]): A list of all keys in the long-term memory.
"""
return {"keys": list(self.archival_memory.keys())}
def archival_memory_key_search(
self, query: str, k: int = 5
) -> Dict[str, List[Tuple[float, str]]]:
"""
Search for key names in the long-term memory that are similar to the query using BM25+ algorithm.
Args:
query (str): The query text to search for.
k (int): [Optional] The number of results to return.
Returns:
ranked_results (List[Tuple[float, str]]): A list of tuples containing the BM25+ score and the key.
"""
keys = deepcopy(list(self.archival_memory.keys()))
return self._similarity_search(query, keys, k)
@@ -0,0 +1,147 @@
import json
from copy import deepcopy
from typing import Dict
from benchmarks.bfcl.executable_runtime.func_source_code.memory_api_metaclass import (
MemoryAPI,
)
MAX_MEMORY_ENTRY_LENGTH = 10000 # 10k characters
class MemoryAPI_rec_sum(MemoryAPI):
"""
A class that provides APIs to manage memory data via recursive summarization.
"""
def __init__(self):
self.memory = ""
self._api_description = """This tool belongs to the memory suite, which provides APIs to manage memory data via recursive summarization."""
self.snapshot_folder = None
def _load_scenario(self, initial_config: dict, long_context: bool = False):
# Set up paths & load snapshots
memory_data = self._prepare_snapshot(initial_config)
# Populate in-memory structures if we have a previous snapshot
if memory_data:
self.memory = deepcopy(memory_data["memory"])
assert isinstance(
self.memory, str
), f"Memory data should be a string, but got {type(self.memory)} instead."
def _flush_memory_to_local_file(self):
"""
Flush (save) current memory to a local JSON file.
"""
# Write the snapshot file for the current test entry
with open(self.snapshot_folder / f"{self.test_id}.json", "w") as f:
json.dump(
{
"memory": self.memory,
},
f,
indent=4,
)
# Update the latest snapshot file content
with open(self.latest_snapshot_file, "w") as f:
json.dump(
{
"memory": self.memory,
},
f,
indent=4,
)
def _dump_core_memory_to_context(self) -> str:
if not self.memory:
return "There is no content in the memory at this point."
return str(self.memory)
def memory_append(self, text: str) -> Dict[str, str]:
"""
Append a new text to the end of the memory.
Args:
text (str): The text to append to the memory.
Returns:
status (str): Status of the operation.
"""
text = str(text)
combined_text = self.memory + text
if len(combined_text) > MAX_MEMORY_ENTRY_LENGTH:
return {
"error": f"Entry will be too long after appending. Please shorten the entry to less than {MAX_MEMORY_ENTRY_LENGTH} characters."
}
self.memory += text
return {"status": "Memory appended."}
def memory_update(self, text: str) -> Dict[str, str]:
"""
Update the memory with new text. This will replace the existing memory content.
Args:
text (str): The new text to set as the memory.
Returns:
status (str): Status of the operation.
"""
text = str(text)
if len(text) > MAX_MEMORY_ENTRY_LENGTH:
return {
"error": f"Entry will be too long after updating. Please shorten the entry to less than {MAX_MEMORY_ENTRY_LENGTH} characters."
}
self.memory = text
return {"status": "Memory updated."}
def memory_clear(self) -> Dict[str, str]:
"""
Clear all content in the memory, including any from previous interactions. This operation is irreversible.
Returns:
status (str): Status of the operation.
"""
self.memory = ""
return {"status": "Short term memory cleared."}
def memory_replace(self, old_text: str, new_text: str) -> Dict[str, str]:
"""
Replace a specific text in the memory with new text.
Args:
old_text (str): The text to be replaced in the memory.
new_text (str): The new text to replace the old text.
Returns:
status (str): Status of the operation.
"""
old_text = str(old_text)
new_text = str(new_text)
if old_text not in self.memory:
return {"error": f"Text '{old_text}' not found in memory."}
if len(new_text) > MAX_MEMORY_ENTRY_LENGTH:
return {
"error": f"Entry will be too long after replacing. Please shorten the entry to less than {MAX_MEMORY_ENTRY_LENGTH} characters."
}
self.memory = self.memory.replace(old_text, new_text)
return {"status": "Memory updated."}
def memory_retrieve(self) -> Dict[str, str]:
"""
Retrieve the current content of the memory.
Returns:
memory_content (str): The current content of the memory.
"""
if not self.memory:
return {"error": "Memory is empty."}
return {"memory_content": self.memory}
@@ -0,0 +1,372 @@
import json
from typing import List, Optional
import numpy as np
from benchmarks.bfcl.executable_runtime.func_source_code.memory_api_metaclass import (
MemoryAPI,
)
# isort: off
# Note: This import order is necessary to avoid segfault issue due to FAISS and PyTorch each load a different OpenMP runtime
# See https://github.com/pytorch/pytorch/issues/149201#issuecomment-2725586827
# TODO: Find a common OpenMP runtime to avoid this issue
from sentence_transformers import SentenceTransformer
import faiss
# isort: on
# https://lilianweng.github.io/posts/2023-06-23-agent/#component-two-memory
MAX_CORE_MEMORY_SIZE = 7
MAX_CORE_MEMORY_ENTRY_LENGTH = 300
MAX_ARCHIVAL_MEMORY_SIZE = 50
MAX_ARCHIVAL_MEMORY_ENTRY_LENGTH = 2000
# Use a global SentenceTransformer model for all vector stores.
ENCODER = SentenceTransformer("all-MiniLM-L6-v2", device="cpu")
ENCODER_DIM = ENCODER.get_sentence_embedding_dimension()
class MemoryAPI_vector(MemoryAPI):
"""
A class that provides APIs to manage short-term and long-term memory data using vector embeddings.
"""
def __init__(self):
self.core_memory = VectorStore(
max_size=MAX_CORE_MEMORY_SIZE,
max_entry_length=MAX_CORE_MEMORY_ENTRY_LENGTH,
)
self.archival_memory = VectorStore(
max_size=MAX_ARCHIVAL_MEMORY_SIZE,
max_entry_length=MAX_ARCHIVAL_MEMORY_ENTRY_LENGTH,
)
self._api_description = """This tool belongs to the memory suite, which provides APIs to interact with a key-value based memory system."""
self.snapshot_folder = None
def _load_scenario(self, initial_config: dict, long_context: bool = False):
# Set up paths & load snapshots
memory_data = self._prepare_snapshot(initial_config)
if memory_data:
self.core_memory.load_from_snapshot(memory_data["core_memory"])
self.archival_memory.load_from_snapshot(memory_data["archival_memory"])
def _flush_memory_to_local_file(self):
"""
Flush (save) current memory (both core and archival) to a local JSON file.
"""
# Write the snapshot file for the current test entry
with open(self.snapshot_folder / f"{self.test_id}.json", "w") as f:
json.dump(
{
"core_memory": self.core_memory.export(),
"archival_memory": self.archival_memory.export(),
},
f,
indent=4,
)
# Update the latest snapshot file content
with open(self.latest_snapshot_file, "w") as f:
json.dump(
{
"core_memory": self.core_memory.export(),
"archival_memory": self.archival_memory.export(),
},
f,
indent=4,
)
def _dump_core_memory_to_context(self) -> str:
if not self.core_memory:
return "There is no content in the core memory at this point."
return json.dumps(self.core_memory._store, indent=4)
def core_memory_add(self, text: str) -> dict[str, str]:
"""
Add a new entry to the core memory.
Args:
text (str): The text to be added to the core memory.
Returns:
id (int): The ID of the added entry, which can be used later for deletion or retrieval.
"""
return self.core_memory.add(text)
def core_memory_remove(self, vec_id: int) -> dict[str, str]:
"""
Remove an entry from the core memory.
Args:
vec_id (int): The ID of the entry to be removed.
Returns:
status (str): Status of the operation.
"""
return self.core_memory.remove(vec_id)
def core_memory_update(self, vec_id: int, new_text: str) -> dict[str, str]:
"""
Update an entry in the core memory.
Args:
vec_id (int): The ID of the entry to be updated.
new_text (str): The new text to replace the old text.
Returns:
status (str): Status of the operation.
"""
return self.core_memory.update(vec_id, new_text)
def core_memory_clear(self) -> dict[str, str]:
"""
Clear all entries in the core memory.
Returns:
status (str): Status of the operation.
"""
return self.core_memory.clear()
def core_memory_retrieve(
self, query: str, top_k: Optional[int] = 5
) -> list[dict[str, str]]:
"""
Retrieve the most similar entries from the core memory.
Args:
query (str): The query text to search for.
top_k (int): [Optional] The number of top similar entries to retrieve.
Returns:
results (list[dict]): A list of dictionaries containing the ID, similarity score, and text of the retrieved entries.
- id (int): The ID of the entry.
- similarity_score (float): The similarity score of the entry with respect to the query.
- text (str): The text of the entry.
"""
return {"result": self.core_memory.retrieve(query, top_k)}
def core_memory_retrieve_all(self) -> list[dict[str, str]]:
"""
Retrieve all entries from the core memory.
Returns:
results (list[dict]): A list of dictionaries containing the ID and text of all entries.
- id (int): The ID of the entry.
- text (str): The text of the entry.
"""
return {"result": self.core_memory.retrieve_all()}
def archival_memory_add(self, text: str) -> dict[str, str]:
"""
Add a new entry to the archival memory.
Args:
text (str): The text to be added to the archival memory.
Returns:
id (int): The ID of the added entry, which can be used later for deletion or retrieval.
"""
return self.archival_memory.add(text)
def archival_memory_remove(self, vec_id: int) -> dict[str, str]:
"""
Remove an entry from the archival memory.
Args:
vec_id (int): The ID of the entry to be removed.
Returns:
status (str): Status of the operation.
"""
return self.archival_memory.remove(vec_id)
def archival_memory_update(self, vec_id: int, new_text: str) -> dict[str, str]:
"""
Update an entry in the archival memory.
Args:
vec_id (int): The ID of the entry to be updated.
new_text (str): The new text to replace the old text.
Returns:
status (str): Status of the operation.
"""
return self.archival_memory.update(vec_id, new_text)
def archival_memory_clear(self) -> dict[str, str]:
"""
Clear all entries in the archival memory.
Returns:
status (str): Status of the operation.
"""
return self.archival_memory.clear()
def archival_memory_retrieve(
self, query: str, top_k: Optional[int] = 5
) -> list[dict[str, str]]:
"""
Retrieve the most similar entries from the archival memory.
Args:
query (str): The query text to search for.
top_k (int): [Optional] The number of top similar entries to retrieve.
Returns:
results (list[dict]): A list of dictionaries containing the ID, similarity score, and text of the retrieved entries.
- id (int): The ID of the entry.
- similarity_score (float): The similarity score of the entry with respect to the query.
- text (str): The text of the entry.
"""
return {"result": self.archival_memory.retrieve(query, top_k)}
def archival_memory_retrieve_all(self) -> list[dict[str, str]]:
"""
Retrieve all entries from the archival memory.
Returns:
results (list[dict]): A list of dictionaries containing the ID and text of all entries.
- id (int): The ID of the entry.
- text (str): The text of the entry.
"""
return {"result": self.archival_memory.retrieve_all()}
class VectorStore:
def __init__(self, max_size, max_entry_length):
self.max_size = max_size
self.max_entry_length = max_entry_length
# Cosine similarity via inner product on L2normalised vectors.
index_flat = faiss.IndexFlatIP(ENCODER_DIM)
self._index = faiss.IndexIDMap(index_flat)
self._store: dict[int, str] = {}
# _next_id will always be unique and sequential
self._next_id: int = 0
def _embed(self, text: str | List[str]) -> np.ndarray:
"""Return an L2-normalised NumPy array suitable for FAISS."""
vecs = ENCODER.encode(
text if isinstance(text, list) else [text], normalize_embeddings=True
)
return np.asarray(vecs, dtype=np.float32)
def add(self, text: str) -> dict[str, str]:
if len(text) > self.max_entry_length:
return {
"error": f"Entry length exceeds maximum length of {self.max_entry_length} characters."
}
if len(self._store) >= self.max_size:
return {
"error": f"Memory size exceeds maximum size of {self.max_size} entries."
}
vec_id = self._next_id
self._next_id += 1
vector = self._embed(text)
self._index.add_with_ids(vector, np.array([vec_id], dtype=np.int64))
self._store[vec_id] = text
return {"id": vec_id}
def remove(self, vec_id: int) -> dict[str, str]:
if vec_id not in self._store:
return {"error": f"ID {vec_id} not present in store."}
self._index.remove_ids(np.array([vec_id], dtype=np.int64))
del self._store[vec_id]
return {"status": f"ID {vec_id} removed from store."}
def update(self, vec_id: int, new_text: str) -> dict[str, str]:
if vec_id not in self._store:
return {"error": f"ID {vec_id} not present in store."}
if len(new_text) > self.max_entry_length:
return {
"error": f"Entry length exceeds maximum length of {self.max_entry_length} characters."
}
self._index.remove_ids(np.array([vec_id], dtype=np.int64))
vector = self._embed(new_text)
self._index.add_with_ids(vector, np.array([vec_id], dtype=np.int64))
self._store[vec_id] = new_text
return {"status": f"ID {vec_id} updated."}
def clear(self) -> dict[str, str]:
self._index.reset()
self._store.clear()
self._next_id = 0
return {"status": "Memory cleared."}
def retrieve(self, query: str, top_k: int = 5) -> list[dict[str, str]]:
"""Return the *top_k* most similar texts.
Return a list of dictionary with keys 'id', 'similarity_score', and 'text'.
"""
if not self._store:
return []
# q_vec has just one row (shape == (1, dim))
q_vec = self._embed(query)
# scores and ids come back with one row each (shape == (1, top_k))
scores, ids = self._index.search(q_vec, min(top_k, len(self._store)))
results = []
for score, vid in zip(scores[0], ids[0]):
# FAISS pads the id slots it cant fill with 1, so we skip those
if vid != -1:
results.append(
{
"id": int(vid),
"similarity_score": float(score),
"text": self._store[int(vid)],
}
)
return results
def retrieve_all(self) -> list[dict[str, str]]:
"""Return all entries in the vector store."""
results = []
for vid, text in self._store.items():
results.append(
{
"id": int(vid),
"text": text,
}
)
return results
def export(self) -> dict:
"""
Export the vector store snapshot to a dictionary.
"""
return {
"next_id": self._next_id,
"store": self._store,
}
def load_from_snapshot(self, snapshot_data: dict) -> None:
"""
Load the vector store from a snapshot.
"""
self._next_id = snapshot_data["next_id"]
self._store = {int(k): v for k, v in snapshot_data["store"].items()}
self._index.reset()
if self._store:
# Re-embed every stored text in one batch
# To keep IDs aligned with vectors, sort by ID
ids = np.array(sorted(self._store.keys()), dtype=np.int64)
texts = [self._store[i] for i in ids]
vectors = self._embed(texts)
# Re-populate the index with the known IDs
self._index.add_with_ids(vectors, ids)
@@ -0,0 +1,320 @@
import random
from copy import deepcopy
from typing import Dict, List, Optional, Union
DEFAULT_STATE = {
"generated_ids": set(),
"user_count": 4,
"user_map": {
"Alice": "USR001",
"Bob": "USR002",
"Catherine": "USR003",
"Daniel": "USR004",
},
"inbox": [
{
"USR002": "My name is Alice. I want to connect.",
},
{
"USR003": "Could you upload the file?",
},
{
"USR004": "Could you upload the file?",
},
],
"message_count": 3,
"current_user": None,
}
class MessageAPI:
"""
A class representing a Message API for managing user interactions in a workspace.
This class provides methods for user management, messaging, and message retrieval
within a specific workspace. It maintains user information, sent messages, and
received messages for each user.
Attributes:
user_map (Dict[str, str]): A mapping of user names to user IDs.
inbox (Dict[int, Dict[str, Union[str, int]]]): A dictionary storing all messages.
message_count (int): The total count of messages in the workspace.
current_user (Optional[str]): The ID of the currently logged-in user.
Methods:
generate_id(): Generate a unique ID for a message.
list_users(): List all users in the workspace.
get_user_id(user: str): Get the user ID for a given username.
login(user_id: str): Log in a user.
send_message(receiver_id: str, message: str): Send a message to another user.
view_messages_sent(): View messages sent by the current user.
delete_message(receiver_id: str): Delete a sent message.
add_contact(name: str, user_id: str): Add a new contact to the workspace.
search_messages(keyword: str): Search for messages containing a keyword.
get_message_stats(): Get messaging statistics for the current user.
"""
def __init__(self):
"""
Initialize the MessageAPI with a workspace ID.
"""
self.generated_ids: set
self.user_count: int
self.user_map: Dict[str, str]
self.inbox: List[Dict[str, str]]
self.message_count: int
self.current_user: Optional[str]
self._api_description = "This tool belongs to the Message API, which is used to manage user interactions in a workspace."
def _load_scenario(self, scenario: dict, long_context=False) -> None:
"""
Load a scenario into the MessageAPI.
Args:
scenario (Dict): A dictionary containing message data.
"""
DEFAULT_STATE_COPY = deepcopy(DEFAULT_STATE)
self._random = random.Random((scenario.get("random_seed", 200191)))
self.generated_ids = scenario.get(
"generated_ids", DEFAULT_STATE_COPY["generated_ids"]
)
self.user_count = scenario.get("user_count", DEFAULT_STATE_COPY["user_count"])
self.user_map = scenario.get("user_map", DEFAULT_STATE_COPY["user_map"])
self.inbox = scenario.get("inbox", DEFAULT_STATE_COPY["inbox"])
self.message_count = scenario.get(
"message_count", DEFAULT_STATE_COPY["message_count"]
)
self.current_user = scenario.get("current_user", DEFAULT_STATE_COPY["current_user"])
def __eq__(self, value: object) -> bool:
if not isinstance(value, MessageAPI):
return False
for attr_name in vars(self):
if attr_name.startswith("_"):
continue
model_attr = getattr(self, attr_name)
ground_truth_attr = getattr(value, attr_name)
if model_attr != ground_truth_attr:
return False
return True
def _generate_id(self):
"""
Generate a unique ID for a message.
Returns:
new_id (int): A unique ID for a message.
"""
new_id = self._random.randint(
10000, 99999
) # first 5 mapped by initial configuration.
while new_id in self.generated_ids:
new_id = self._random.randint(10000, 99999)
self.generated_ids.add(new_id)
return {"new_id": new_id}
def list_users(self) -> Dict[str, List[str]]:
"""
List all users in the workspace.
Returns:
user_list (List[str]): List of all users in the workspace.
"""
return {"user_list": list(self.user_map.keys())}
def get_user_id(self, user: str) -> Dict[str, Optional[str]]:
"""
Get user ID from user name.
Args:
user (str): User name of the user.
Returns:
user_id (str): User ID of the user
"""
if user not in self.user_map:
return {"error": f"User '{user}' not found in the workspace."}
return {"user_id": self.user_map.get(user)}
def message_login(self, user_id: str) -> Dict[str, Union[str, bool]]:
"""
Log in a user with the given user ID to messeage application.
Args:
user_id (str): User ID of the user to log in.
Returns:
login_status (bool): True if login was successful, False otherwise.
message (str): A message describing the result of the login attempt.
"""
if user_id not in [id for id in self.user_map.values()]:
return {"login_status": False, "message": f"User ID '{user_id}' not found."}
self.current_user = user_id
return {
"login_status": True,
"message": f"User '{user_id}' logged in successfully.",
}
def message_get_login_status(self) -> Dict[str, bool]:
"""
Get the login status of the current user.
Returns:
login_status (bool): True if the current user is logged in, False otherwise.
"""
return {"login_status": bool(self.current_user)}
def send_message(self, receiver_id: str, message: str) -> Dict[str, Union[str, bool]]:
"""
Send a message to a user.
Args:
receiver_id (str): User ID of the user to send the message to.
message (str): Message to be sent.
Returns:
sent_status (bool): True if the message was sent successfully, False otherwise.
message_id (int): ID of the sent message.
message (str): A message describing the result of the send attempt.
"""
# Check if there is a current user logged in
if not self.current_user:
return {"error": "No user is currently logged in."}
# Validate receiver existence
if receiver_id not in self.user_map.values():
return {"error": f"Receiver ID '{receiver_id}' not found."}
# Generate a unique message ID
message_id = self._generate_id()
# Store the message in the inbox
self.inbox.append({receiver_id: message})
self.message_count += 1
return {
"sent_status": True,
"message_id": message_id,
"message": f"Message sent to '{receiver_id}' successfully.",
}
def delete_message(self, receiver_id: str) -> Dict[str, Union[bool, str]]:
"""
Delete the latest message sent to a receiver.
Args:
receiver_id (str): User ID of the user to send the message to.
Returns:
deleted_status (bool): True if the message was deleted successfully, False otherwise.
receiver_id (str): ID of the receiver of the deleted message.
message (str): A message describing the result of the deletion attempt.
"""
if not self.current_user:
return {"error": "No user is currently logged in."}
# Loop through the inbox in reverse order to find the first message sent to the receiver
for message in self.inbox[::-1]:
receiver, _ = list(message.items())[0]
if receiver == receiver_id:
self.inbox.remove(message)
return {
"deleted_status": True,
"receiver_id": receiver,
"message": f"Receiver {receiver_id}'s latest message deleted successfully.",
}
return {"error": f"Receiver ID {receiver_id} not found."}
def view_messages_sent(self) -> Dict[str, Union[Dict[str, List[str]], str]]:
"""
View all historical messages sent by the current user.
Returns:
messages (Dict): Dictionary of messages grouped by receiver An example of the messages dictionary is {"USR001":["Hello"],"USR002":["World"]}.
"""
if not self.current_user:
return {"error": "No user is currently logged in."}
# Dictionary to collect messages grouped by receiver
sent_messages = {}
# Loop through the inbox and collect messages sent by the current user
for message in self.inbox:
receiver, message_content = list(message.items())[0]
if receiver not in sent_messages:
sent_messages[receiver] = [message_content]
else:
sent_messages[receiver].append(message_content)
return {"messages": sent_messages}
def add_contact(self, user_name: str) -> Dict[str, Union[bool, str]]:
"""
Add a contact to the workspace.
Args:
user_name (str): User name of contact to be added.
Returns:
added_status (bool): True if the contact was added successfully, False otherwise.
user_id (str): User ID of the added contact.
message (str): A message describing the result of the addition attempt.
"""
if user_name in self.user_map:
return {"error": f"User name '{user_name}' already exists."}
self.user_count += 1
user_id = f"USR{str(self.user_count).zfill(3)}"
if user_id in self.user_map.values():
return {"error": f"User ID '{user_id}' already exists."}
self.user_map[user_name] = user_id
return {
"added_status": True,
"user_id": user_id,
"message": f"Contact '{user_name}' added successfully.",
}
def search_messages(
self, keyword: str
) -> Dict[str, Union[List[Dict[str, Union[str, List[str]]]], str]]:
"""
Search for messages containing a specific keyword.
Args:
keyword (str): The keyword to search for in messages.
Returns:
results (List[Dict]): List of dictionaries containing matching messages.
- receiver_id (str): User ID of the receiver of the message.
- message (str): The message containing the keyword.
"""
if not self.current_user:
return {"error": "No user is currently logged in."}
keyword_lower = keyword.lower()
results = []
# Iterate through the inbox to search for the keyword in messages
# for message_id, message_data in self.inbox.items():
for message_data in self.inbox:
receiver_id, message_content = list(message_data.items())[0]
if keyword_lower in message_content.lower():
results.append(
{
"receiver_id": receiver_id,
"message": message_content,
}
)
return {"results": results}
def get_message_stats(self) -> Dict[str, Union[Dict[str, int], str]]:
"""
Get statistics about messages for the current user.
Returns:
stats (Dict): Dictionary containing message statistics.
- received_count (int): Number of messages received by the current user.
- total_contacts (int): Total number of contacts the user has interacted with.
"""
if not self.current_user:
return {"error": "No user is currently logged in."}
sent_count = 0
received_count = 0
contacts = set()
# Loop through the inbox to calculate stats
for message_data in self.inbox:
receiver_id, message_content = list(message_data.items())[0]
received_count += 1
contacts.add(receiver_id)
total_contacts = len(contacts)
return {
"stats": {
"received_count": received_count,
"total_contacts": total_contacts,
}
}
@@ -0,0 +1,313 @@
from copy import deepcopy
from typing import Dict, List, Union
DEFAULT_STATE = {
"username": "john",
"password": "john123",
"authenticated": False,
"tweets": {},
"comments": {},
"retweets": {},
"following_list": ["alice", "bob"],
"tweet_counter": 0,
}
class TwitterAPI:
def __init__(self):
self.username: str
self.password: str
self.authenticated: bool
self.tweets: Dict[int, Dict[str, Union[int, str, List[str]]]]
self.comments: Dict[int, List[Dict[str, str]]]
self.retweets: Dict[str, List[int]]
self.following_list: List[str]
# tweet_counter is used to assign unique IDs to tweets, it might not be the same as the length of the tweets list for different scenarios
self.tweet_counter: int
self._api_description = "This tool belongs to the TwitterAPI, which provides core functionality for posting tweets, retweeting, commenting, and following users on Twitter."
def _load_scenario(self, scenario: dict, long_context=False) -> None:
"""
Load a scenario into the TwitterAPI instance.
Args:
scenario (dict): A dictionary containing Twitter data.
"""
DEFAULT_STATE_COPY = deepcopy(DEFAULT_STATE)
self.username = scenario.get("username", DEFAULT_STATE_COPY["username"])
self.password = scenario.get("password", DEFAULT_STATE_COPY["password"])
self.authenticated = scenario.get(
"authenticated", DEFAULT_STATE_COPY["authenticated"]
)
self.tweets = scenario.get("tweets", DEFAULT_STATE_COPY["tweets"])
self.tweets = {int(k): v for k, v in self.tweets.items()} # Convert tweet keys from string to int from loaded scenario
self.comments = scenario.get("comments", DEFAULT_STATE_COPY["comments"])
self.retweets = scenario.get("retweets", DEFAULT_STATE_COPY["retweets"])
self.following_list = scenario.get(
"following_list", DEFAULT_STATE_COPY["following_list"]
)
self.tweet_counter = scenario.get(
"tweet_counter", DEFAULT_STATE_COPY["tweet_counter"]
)
def authenticate_twitter(self, username: str, password: str) -> Dict[str, bool]:
"""
Authenticate a user with username and password.
Args:
username (str): Username of the user.
password (str): Password of the user.
Returns:
authentication_status (bool): True if authenticated, False otherwise.
"""
if username == self.username and password == self.password:
self.authenticated = True
return {"authentication_status": True}
return {"authentication_status": False}
def posting_get_login_status(self) -> Dict[str, Union[bool, str]]:
"""
Get the login status of the current user.
Returns:
login_status (bool): True if the current user is logged in, False otherwise.
"""
return {"login_status": bool(self.authenticated)}
def post_tweet(
self, content: str, tags: List[str] = [], mentions: List[str] = []
) -> Dict[str, Union[int, str, List[str]]]:
"""
Post a tweet for the authenticated user.
Args:
content (str): Content of the tweet.
tags (List[str]): [Optional] List of tags for the tweet. Tag name should start with #. This is only relevant if the user wants to add tags to the tweet.
mentions (List[str]): [Optional] List of users mentioned in the tweet. Mention name should start with @. This is only relevant if the user wants to add mentions to the tweet.
Returns:
id (int): ID of the posted tweet.
username (str): Username of the poster.
content (str): Content of the tweet.
tags (List[str]): List of tags associated with the tweet.
mentions (List[str]): List of users mentioned in the tweet.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please authenticate before posting."}
tweet = {
"id": self.tweet_counter,
"username": self.username,
"content": content,
"tags": tags,
"mentions": mentions,
}
self.tweets[self.tweet_counter] = tweet
self.tweet_counter += 1
return tweet
def retweet(self, tweet_id: int) -> Dict[str, str]:
"""
Retweet a tweet for the authenticated user.
Args:
tweet_id (int): ID of the tweet to retweet.
Returns:
retweet_status (str): Status of the retweet action.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please authenticate before retweeting."}
if tweet_id not in self.tweets:
return {"error": f"Tweet with ID {tweet_id} not found."}
if self.username not in self.retweets:
self.retweets[self.username] = []
if tweet_id in self.retweets[self.username]:
return {"retweet_status": "Already retweeted"}
self.retweets[self.username].append(tweet_id)
return {"retweet_status": "Successfully retweeted"}
def comment(self, tweet_id: int, comment_content: str) -> Dict[str, str]:
"""
Comment on a tweet for the authenticated user.
Args:
tweet_id (int): ID of the tweet to comment on.
comment_content (str): Content of the comment.
Returns:
comment_status (str): Status of the comment action.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please authenticate before commenting."}
if tweet_id not in self.tweets:
return {"error": f"Tweet with ID {tweet_id} not found."}
if tweet_id not in self.comments:
self.comments[tweet_id] = []
self.comments[tweet_id].append(
{"username": self.username, "content": comment_content}
)
return {"comment_status": "Comment added successfully"}
def mention(self, tweet_id: int, mentioned_usernames: List[str]) -> Dict[str, str]:
"""
Mention specified users in a tweet.
Args:
tweet_id (int): ID of the tweet where users are mentioned.
mentioned_usernames (List[str]): List of usernames to be mentioned.
Returns:
mention_status (str): Status of the mention action.
"""
if tweet_id not in self.tweets:
return {"error": f"Tweet with ID {tweet_id} not found."}
tweet = self.tweets[tweet_id]
tweet["mentions"].extend(mentioned_usernames)
return {"mention_status": "Users mentioned successfully"}
def follow_user(self, username_to_follow: str) -> Dict[str, bool]:
"""
Follow a user for the authenticated user.
Args:
username_to_follow (str): Username of the user to follow.
Returns:
follow_status (bool): True if followed, False if already following.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please authenticate before following."}
if username_to_follow in self.following_list:
return {"follow_status": False}
self.following_list.append(username_to_follow)
return {"follow_status": True}
def list_all_following(self) -> Dict[str, List[str]]:
"""
List all users that the authenticated user is following.
Returns:
following_list (List[str]): List of all users that the authenticated user is following.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please authenticate before listing following."}
return {"following_list": self.following_list}
def unfollow_user(self, username_to_unfollow: str) -> Dict[str, bool]:
"""
Unfollow a user for the authenticated user.
Args:
username_to_unfollow (str): Username of the user to unfollow.
Returns:
unfollow_status (bool): True if unfollowed, False if not following.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please authenticate before unfollowing."}
if username_to_unfollow not in self.following_list:
return {"unfollow_status": False}
self.following_list.remove(username_to_unfollow)
return {"unfollow_status": True}
def get_tweet(self, tweet_id: int) -> Dict[str, Union[int, str, List[str]]]:
"""
Retrieve a specific tweet.
Args:
tweet_id (int): ID of the tweet to retrieve.
Returns:
id (int): ID of the retrieved tweet.
username (str): Username of the tweet's author.
content (str): Content of the tweet.
tags (List[str]): List of tags associated with the tweet.
mentions (List[str]): List of users mentioned in the tweet.
"""
if tweet_id not in self.tweets:
return {"error": f"Tweet with ID {tweet_id} not found."}
return self.tweets[tweet_id]
def get_user_tweets(self, username: str) -> List[Dict[str, Union[int, str, List[str]]]]:
"""
Retrieve all tweets from a specific user.
Args:
username (str): Username of the user whose tweets to retrieve.
Returns:
user_tweets (List[Dict]): List of dictionaries, each containing tweet information.
- id (int): ID of the retrieved tweet.
- username (str): Username of the tweet's author.
- content (str): Content of the tweet.
- tags (List[str]): List of tags associated with the tweet.
- mentions (List[str]): List of users mentioned in the tweet.
"""
return [tweet for tweet in self.tweets.values() if tweet["username"] == username]
def search_tweets(self, keyword: str) -> List[Dict[str, Union[int, str, List[str]]]]:
"""
Search for tweets containing a specific keyword.
Args:
keyword (str): Keyword to search for in the content of the tweets.
Returns:
matching_tweets (List[Dict]): List of dictionaries, each containing tweet information.
- id (int): ID of the retrieved tweet.
- username (str): Username of the tweet's author.
- content (str): Content of the tweet.
- tags (List[str]): List of tags associated with the tweet.
- mentions (List[str]): List of users mentioned in the tweet.
"""
return [
tweet
for tweet in self.tweets.values()
if keyword.lower() in tweet["content"].lower()
or keyword.lower() in [tag.lower() for tag in tweet["tags"]]
]
def get_tweet_comments(self, tweet_id: int) -> List[Dict[str, str]]:
"""
Retrieve all comments for a specific tweet.
Args:
tweet_id (int): ID of the tweet to retrieve comments for.
Returns:
comments (List[Dict]): List of dictionaries, each containing comment information.
- username (str): Username of the commenter.
- content (str): Content of the comment.
"""
if tweet_id not in self.tweets:
return {"error": f"Tweet with ID {tweet_id} not found."}
return self.comments.get(tweet_id, [])
def get_user_stats(self, username: str) -> Dict[str, int]:
"""
Get statistics for a specific user.
Args:
username (str): Username of the user to get statistics for.
Returns:
tweet_count (int): Number of tweets posted by the user.
following_count (int): Number of users the specified user is following.
retweet_count (int): Number of retweets made by the user.
"""
tweet_count = len(
[tweet for tweet in self.tweets.values() if tweet["username"] == username]
)
following_count = len(self.following_list) if username == self.username else 0
retweet_count = len(self.retweets.get(username, []))
return {
"tweet_count": tweet_count,
"following_count": following_count,
"retweet_count": retweet_count,
}
@@ -0,0 +1,265 @@
from copy import deepcopy
from typing import Dict, List, Optional, Union
DEFAULT_STATE = {
"ticket_queue": [],
"ticket_counter": 1,
"current_user": None,
}
class TicketAPI:
"""
A class representing the Ticket API for managing support tickets.
This class provides methods for creating, retrieving, and managing
support tickets within a ticketing system. It maintains a queue of
tickets and handles ticket-related operations such as creation,
status updates, and retrieval.
Attributes:
ticket_queue (List[Dict[str, Union[int, str]]]): A list of ticket dictionaries.
ticket_counter (int): A counter for generating unique ticket IDs.
current_user (Optional[str]): The currently authenticated user.
"""
def __init__(self):
"""
Initialize the TicketAPI instance.
"""
self.ticket_queue: List[Dict[str, Union[int, str]]]
self.ticket_counter: int
self.current_user: Optional[str]
self._api_description = "This tool belongs to the ticketing system that is part of a company, which allows users to create, view, and manage support business tickets."
def _load_scenario(self, scenario: dict, long_context=False) -> None:
"""
Load a scenario into the ticket queue.
Args:
scenario (Dict): A dictionary containing ticket data.
"""
DEFAULT_STATE_COPY = deepcopy(DEFAULT_STATE)
self.ticket_queue = scenario.get("ticket_queue", DEFAULT_STATE_COPY["ticket_queue"])
self.ticket_counter = scenario.get(
"ticket_counter", DEFAULT_STATE_COPY["ticket_counter"]
)
self.current_user = scenario.get("current_user", DEFAULT_STATE_COPY["current_user"])
def create_ticket(
self, title: str, description: str = "", priority: int = 1
) -> Dict[str, Union[int, str]]:
"""
Create a ticket in the system and queue it.
Args:
title (str): Title of the ticket.
description (str): Description of the ticket. Defaults to an empty string.
priority (int): Priority of the ticket, from 1 to 5. Defaults to 1. 5 is the highest priority.
Returns:
id (int): Unique identifier of the ticket.
title (str): Title of the ticket.
description (str): Description of the ticket.
status (str): Current status of the ticket.
priority (int): Priority level of the ticket.
"""
if not self.current_user:
return {"error": "User not authenticated. Please log in to create a ticket."}
if priority < 1 or priority > 5:
return {"error": "Invalid priority. Priority must be between 1 and 5."}
ticket = {
"id": self.ticket_counter,
"title": title,
"description": description,
"status": "Open",
"priority": priority,
"created_by": self.current_user,
}
self.ticket_queue.append(ticket)
self.ticket_counter += 1
return ticket
def get_ticket(self, ticket_id: int) -> Dict[str, Union[int, str]]:
"""
Get a specific ticket by its ID.
Args:
ticket_id (int): ID of the ticket to retrieve.
Returns:
id (int): Unique identifier of the ticket.
title (str): Title of the ticket.
description (str): Description of the ticket.
status (str): Current status of the ticket.
priority (int): Priority level of the ticket.
created_by (str): Username of the ticket creator.
"""
ticket = self._find_ticket(ticket_id)
if not ticket:
return {"error": f"Ticket with ID {ticket_id} not found."}
return ticket
def close_ticket(self, ticket_id: int) -> Dict[str, str]:
"""
Close a ticket.
Args:
ticket_id (int): ID of the ticket to be closed.
Returns:
status (str): Status of the close operation.
"""
ticket = self._find_ticket(ticket_id)
if not ticket:
return {"error": f"Ticket with ID {ticket_id} not found."}
if ticket["status"] == "Closed":
return {"error": f"Ticket with ID {ticket_id} is already closed."}
ticket["status"] = "Closed"
return {"status": f"Ticket {ticket_id} has been closed successfully."}
def resolve_ticket(self, ticket_id: int, resolution: str) -> Dict[str, str]:
"""
Resolve a ticket with a resolution.
Args:
ticket_id (int): ID of the ticket to be resolved.
resolution (str): Resolution details for the ticket.
Returns:
status (str): Status of the resolve operation.
"""
ticket = self._find_ticket(ticket_id)
if not ticket:
return {"error": f"Ticket with ID {ticket_id} not found."}
if ticket["status"] == "Resolved":
return {"error": f"Ticket with ID {ticket_id} is already resolved."}
ticket["status"] = "Resolved"
ticket["resolution"] = resolution
return {"status": f"Ticket {ticket_id} has been resolved successfully."}
def edit_ticket(
self, ticket_id: int, updates: Dict[str, Optional[Union[str, int]]]
) -> Dict[str, str]:
"""
Modify the details of an existing ticket.
Args:
ticket_id (int): ID of the ticket to be changed.
updates (Dict): Dictionary containing the fields to be updated.
- title (str): [Optional] New title for the ticket.
- description (str): [Optional] New description for the ticket.
- status (str): [Optional] New status for the ticket.
- priority (int): [Optional] New priority for the ticket.
Returns:
status (str): Status of the update operation.
"""
ticket = self._find_ticket(ticket_id)
if not ticket:
return {"error": f"Ticket with ID {ticket_id} not found."}
valid_fields = {"title", "description", "status", "priority"}
invalid_fields = set(updates.keys()) - valid_fields
if invalid_fields:
return {"error": f"Invalid fields for update: {', '.join(invalid_fields)}"}
for key, value in updates.items():
if value is not None:
ticket[key] = value
return {"status": f"Ticket {ticket_id} has been updated successfully."}
def _find_ticket(self, ticket_id: int) -> Optional[Dict[str, Union[int, str]]]:
"""
Find a ticket by its ID.
Args:
ticket_id (int): ID of the ticket to find.
Returns:
id (int): Unique identifier of the ticket.
title (str): Title of the ticket.
description (str): Description of the ticket.
status (str): Current status of the ticket.
priority (int): Priority level of the ticket.
created_by (str): Username of the ticket creator.
"""
for ticket in self.ticket_queue:
if ticket["id"] == ticket_id:
return ticket
return None
def ticket_login(self, username: str, password: str) -> Dict[str, bool]:
"""
Authenticate a user for ticket system.
Args:
username (str): Username of the user.
password (str): Password of the user.
Returns:
success (bool): True if login was successful, False otherwise.
"""
# In a real system, you would validate the credentials against a database
if username and password: # Simplified authentication
self.current_user = username
return {"success": True}
return {"success": False}
def ticket_get_login_status(self) -> Dict[str, bool]:
"""
Get the login status of the currently authenticated user.
Returns:
login_status (bool): True if a user is logged in, False otherwise.
"""
return {"login_status": bool(self.current_user)}
def logout(self) -> Dict[str, bool]:
"""
Log out the current user.
Returns:
success (bool): True if logout was successful, False otherwise.
"""
if self.current_user:
self.current_user = None
return {"success": True}
return {"success": False}
def get_user_tickets(
self, status: Optional[str] = None
) -> List[Dict[str, Union[int, str]]]:
"""
Get all tickets created by the current user, optionally filtered by status.
Args:
status (str): [Optional] Status to filter tickets by. If None, return all tickets.
Returns:
id (int): Unique identifier of the ticket.
title (str): Title of the ticket.
description (str): Description of the ticket.
status (str): Current status of the ticket.
priority (int): Priority level of the ticket.
created_by (str): Username of the ticket
"""
if not self.current_user:
return [{"error": "User not authenticated. Please log in to view tickets."}]
user_tickets = [
ticket
for ticket in self.ticket_queue
if ticket["created_by"] == self.current_user
]
if status:
user_tickets = [
ticket
for ticket in user_tickets
if ticket["status"].lower() == status.lower()
]
return user_tickets
@@ -0,0 +1,683 @@
import random
from copy import deepcopy
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Union
from benchmarks.bfcl.executable_runtime.func_source_code.long_context import (
AUTOMOBILE_EXTENSION,
MA_5_EXTENSION,
MA_20_EXTENSION,
ORDER_DETAIL_EXTENSION,
TECHNOLOGY_EXTENSION,
TRANSACTION_HISTORY_EXTENSION,
WATCH_LIST_EXTENSION,
)
CURRENT_TIME = datetime(2024, 9, 1, 10, 30)
DEFAULT_STATE = {
"orders": {
12345: {
"id": 12345,
"order_type": "Buy",
"symbol": "AAPL",
"price": 210.65,
"amount": 10,
"status": "Completed",
},
12446: {
"id": 12446,
"order_type": "Sell",
"symbol": "GOOG",
"price": 2840.56,
"amount": 5,
"status": "Pending",
},
},
"account_info": {
"account_id": 12345,
"balance": 10000.0,
"binding_card": 1974202140965533,
},
"authenticated": False,
"market_status": "Closed",
"order_counter": 12446,
"stocks": {
"AAPL": {
"price": 227.16,
"percent_change": 0.17,
"volume": 2.552,
"MA(5)": 227.11,
"MA(20)": 227.09,
},
"GOOG": {
"price": 2840.34,
"percent_change": 0.24,
"volume": 1.123,
"MA(5)": 2835.67,
"MA(20)": 2842.15,
},
"TSLA": {
"price": 667.92,
"percent_change": -0.12,
"volume": 1.654,
"MA(5)": 671.15,
"MA(20)": 668.20,
},
"MSFT": {
"price": 310.23,
"percent_change": 0.09,
"volume": 3.234,
"MA(5)": 309.88,
"MA(20)": 310.11,
},
"NVDA": {
"price": 220.34,
"percent_change": 0.34,
"volume": 1.234,
"MA(5)": 220.45,
"MA(20)": 220.67,
},
"ALPH": {
"price": 1320.45,
"percent_change": -0.08,
"volume": 1.567,
"MA(5)": 1321.12,
"MA(20)": 1325.78,
},
"OMEG": {
"price": 457.23,
"percent_change": 0.12,
"volume": 2.345,
"MA(5)": 456.78,
"MA(20)": 458.12,
},
"QUAS": {
"price": 725.89,
"percent_change": -0.03,
"volume": 1.789,
"MA(5)": 726.45,
"MA(20)": 728.00,
},
"NEPT": {
"price": 88.34,
"percent_change": 0.19,
"volume": 0.654,
"MA(5)": 88.21,
"MA(20)": 88.67,
},
"SYNX": {
"price": 345.67,
"percent_change": 0.11,
"volume": 2.112,
"MA(5)": 345.34,
"MA(20)": 346.12,
},
"ZETA": {
"price": 22.09,
"percent_change": -0.05,
"volume": 0.789,
"MA(5)": 22.12,
"MA(20)": 22.34,
},
},
"watch_list": ["NVDA"],
"transaction_history": [],
"random_seed": 1053520,
}
class TradingBot:
"""
A class representing a trading bot for executing stock trades and managing a trading account.
Attributes:
orders (Dict[int, Dict[str, Union[str, float, int]]]): A dictionary of orders for purchasing and selling of stock, keyed by order ID.
account_info (Dict[str, Union[int, float]]): Information about the trading account.
authenticated (bool): Whether the user is currently authenticated.
market_status (str): The current status of the market ('Open' or 'Closed').
order_counter (int): A counter for generating unique order IDs.
stocks (Dict[str, Dict[str, Union[float, int]]]): Information about various stocks.
watch_list (List[str]): A list of stock symbols being watched.
transaction_history (List[Dict[str, Union[str, float, int]]]): A history of trading account related transactions.
"""
def __init__(self):
"""
Initialize the TradingBot instance.
"""
self.orders: Dict[int, Dict[str, Union[str, float, int]]]
self.account_info: Dict[str, Union[int, float]]
self.authenticated: bool
self.market_status: str
self.order_counter: int
self.stocks: Dict[str, Dict[str, Union[float, int]]]
self.watch_list: List[str]
self.transaction_history: List[Dict[str, Union[str, float, int]]]
self._api_description = "This tool belongs to the trading system, which allows users to trade stocks, manage their account, and view stock information."
def _load_scenario(self, scenario: dict, long_context=False) -> None:
"""
Load a scenario into the TradingBot.
Args:
scenario (dict): A scenario dictionary containing data to load.
"""
DEFAULT_STATE_COPY = deepcopy(DEFAULT_STATE)
self.orders = scenario.get("orders", DEFAULT_STATE_COPY["orders"])
# Convert all string keys that can be interpreted as integers to integer keys
self.orders = {
int(k) if isinstance(k, str) and k.isdigit() else k: v
for k, v in self.orders.items()
}
self.account_info = scenario.get("account_info", DEFAULT_STATE_COPY["account_info"])
self.authenticated = scenario.get(
"authenticated", DEFAULT_STATE_COPY["authenticated"]
)
self.market_status = scenario.get(
"market_status", DEFAULT_STATE_COPY["market_status"]
)
self.order_counter = scenario.get(
"order_counter", DEFAULT_STATE_COPY["order_counter"]
) # Start counter from the next order ID
self.stocks = scenario.get("stocks", DEFAULT_STATE_COPY["stocks"])
self.watch_list = scenario.get("watch_list", DEFAULT_STATE_COPY["watch_list"])
self.transaction_history = scenario.get(
"transaction_history", DEFAULT_STATE_COPY["transaction_history"]
)
self.long_context = long_context
self._random = random.Random(
(scenario.get("random_seed", DEFAULT_STATE_COPY["random_seed"]))
)
def _generate_transaction_timestamp(self) -> str:
"""
Generate a timestamp for a transaction.
Returns:
timestamp (str): A formatted timestamp string.
"""
# Define the start and end dates for the range
start_date = CURRENT_TIME
end_date = CURRENT_TIME + timedelta(days=1)
start_timestamp = int(start_date.timestamp())
end_timestamp = int(end_date.timestamp())
# Generate a random timestamp within the range
random_timestamp = self._random.randint(start_timestamp, end_timestamp)
# Convert the random timestamp to a datetime object
random_date = datetime.fromtimestamp(random_timestamp)
return random_date.strftime("%Y-%m-%d %H:%M:%S")
def get_current_time(self) -> Dict[str, str]:
"""
Get the current time.
Returns:
current_time (str): Current time in HH:MM AM/PM format.
"""
return {"current_time": CURRENT_TIME.strftime("%I:%M %p")}
def get_symbol_by_name(self, name: str) -> Dict[str, str]:
"""
Get the symbol of a stock by company name.
Args:
name (str): Name of the company.
Returns:
symbol (str): Symbol of the stock or "Stock not found" if not available.
"""
symbol_map = {
"Apple": "AAPL",
"Google": "GOOG",
"Tesla": "TSLA",
"Microsoft": "MSFT",
"Nvidia": "NVDA",
"Zeta Corp": "ZETA",
"Alpha Tech": "ALPH",
"Omega Industries": "OMEG",
"Quasar Ltd.": "QUAS",
"Neptune Systems": "NEPT",
"Synex Solutions": "SYNX",
"Amazon": "AMZN",
"Gorilla": "GORI",
}
return {"symbol": symbol_map.get(name, "Stock not found")}
def get_stock_info(self, symbol: str) -> Dict[str, Union[float, int, str]]:
"""
Get the details of a stock.
Args:
symbol (str): Symbol that uniquely identifies the stock.
Returns:
price (float): Current price of the stock.
percent_change (float): Percentage change in stock price.
volume (float): Trading volume of the stock.
MA(5) (float): 5-day Moving Average of the stock.
MA(20) (float): 20-day Moving Average of the stock.
"""
if symbol not in self.stocks:
return {"error": f"Stock with symbol '{symbol}' not found."}
if self.long_context:
stock = self.stocks[symbol].copy()
stock["MA(5)"] = MA_5_EXTENSION
stock["MA(20)"] = MA_20_EXTENSION
return stock
return self.stocks[symbol]
def get_order_details(self, order_id: int) -> Dict[str, Union[str, float, int]]:
"""
Get the details of an order.
Args:
order_id (int): ID of the order.
Returns:
id (int): ID of the order.
order_type (str): Type of the order.
symbol (str): Symbol of the stock in the order.
price (float): Price at which the order was placed.
amount (int): Number of shares in the order.
status (str): Current status of the order. [Enum]: ["Open", "Pending", "Completed", "Cancelled"]
"""
if order_id not in self.orders:
return {
"error": f"Order with ID {order_id} not found."
+ "Here is the list of orders_id: "
+ str(list(self.orders.keys()))
}
if self.long_context:
order = self.orders[order_id].copy()
symbol = order["symbol"]
formatted_extension = {}
for key, value in ORDER_DETAIL_EXTENSION.items():
try:
formatted_extension[key] = value.format(symbol=symbol)
except KeyError as e:
return {"error": f"KeyError during formatting: {str(e)}"}
# Add formatted extension to the order metadata
order["metadata"] = formatted_extension
return order
return self.orders[order_id]
def cancel_order(self, order_id: int) -> Dict[str, Union[int, str]]:
"""
Cancel an order.
Args:
order_id (int): ID of the order to cancel.
Returns:
order_id (int): ID of the cancelled order.
status (str): New status of the order after cancellation attempt.
"""
if order_id not in self.orders:
return {"error": f"Order with ID {order_id} not found."}
if self.orders[order_id]["status"] == "Completed":
return {"error": f"Can't cancel order {order_id}. Order is already completed."}
self.orders[order_id]["status"] = "Cancelled"
return {"order_id": order_id, "status": "Cancelled"}
def place_order(
self, order_type: str, symbol: str, price: float, amount: int
) -> Dict[str, Union[int, str, float]]:
"""
Place an order.
Args:
order_type (str): Type of the order (Buy/Sell).
symbol (str): Symbol of the stock to trade.
price (float): Price at which to place the order.
amount (int): Number of shares to trade.
Returns:
order_id (int): ID of the newly placed order.
order_type (str): Type of the order (Buy/Sell).
status (str): Initial status of the order.
price (float): Price at which the order was placed.
amount (int): Number of shares in the order.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please log in to place an order."}
if symbol not in self.stocks:
return {"error": f"Invalid stock symbol: {symbol}"}
if price <= 0 or amount <= 0:
return {"error": "Price and amount must be positive values."}
# Ensure sufficient funds for buy orders
if order_type.lower() == "buy":
total_cost = float(price) * int(amount)
if total_cost > self.account_info.get("balance", 0):
return {
"error": (
"Insufficient funds: required "
f"${total_cost:.2f} but only ${self.account_info.get('balance', 0):.2f} available."
)
}
price = float(price)
order_id = self.order_counter
self.orders[order_id] = {
"id": order_id,
"order_type": order_type,
"symbol": symbol,
"price": price,
"amount": amount,
"status": "Open",
}
self.order_counter += 1
# We return the status as "Pending" to indicate that the order has been placed but not yet executed
# When polled later, the status will show as 'Open'
# This is to simulate the delay between placing an order and it being executed
return {
"order_id": order_id,
"order_type": order_type,
"status": "Pending",
"price": price,
"amount": amount,
}
def withdraw_funds(self, amount: float) -> Dict[str, Union[str, float]]:
"""
Withdraw funds from the account balance.
Args:
amount (float): Amount to withdraw from the account.
Returns:
status (str): Status of the transaction.
new_balance (float): Updated account balance after the transaction.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please log in to make a transaction."}
if self.market_status != "Open":
return {"error": "Market is closed. Transactions are not allowed."}
if amount <= 0:
return {"error": "Transaction amount must be positive."}
if amount > self.account_info["balance"]:
return {"error": "Insufficient funds for withdrawal."}
self.account_info["balance"] -= amount
self.transaction_history.append(
{
"type": "withdrawal",
"amount": amount,
"timestamp": self._generate_transaction_timestamp(),
}
)
return {
"status": "Withdrawal successful",
"new_balance": self.account_info["balance"],
}
def get_account_info(self) -> Dict[str, Union[int, float]]:
"""
Get account information.
Returns:
account_id (int): ID of the account.
balance (float): Current balance of the account.
binding_card (int): Card number associated with the account.
"""
if not self.authenticated:
return {
"error": "User not authenticated. Please log in to view account information."
}
return self.account_info
def trading_login(self, username: str, password: str) -> Dict[str, str]:
"""
Handle user login.
Args:
username (str): Username for authentication.
password (str): Password for authentication.
Returns:
status (str): Login status message.
"""
if self.authenticated:
return {"status": "Already logged in"}
# In a real system, we would validate the username and password here
self.authenticated = True
return {"status": "Logged in successfully"}
def trading_get_login_status(self) -> Dict[str, bool]:
"""
Get the login status.
Returns:
status (bool): Login status.
"""
return {"status": bool(self.authenticated)}
def trading_logout(self) -> Dict[str, str]:
"""
Handle user logout for trading system.
Returns:
status (str): Logout status message.
"""
if not self.authenticated:
return {"status": "No user is currently logged in"}
self.authenticated = False
return {"status": "Logged out successfully"}
def fund_account(self, amount: float) -> Dict[str, Union[str, float]]:
"""
Fund the account with the specified amount.
Args:
amount (float): Amount to fund the account with.
Returns:
status (str): Status of the funding operation.
new_balance (float): Updated account balance after funding.
"""
if not self.authenticated:
return {"error": "User not authenticated. Please log in to fund the account."}
if amount <= 0:
return {"error": "Funding amount must be positive."}
self.account_info["balance"] += amount
self.transaction_history.append(
{
"type": "deposit",
"amount": amount,
"timestamp": self._generate_transaction_timestamp(),
}
)
return {
"status": "Account funded successfully",
"new_balance": self.account_info["balance"],
}
def remove_stock_from_watchlist(self, symbol: str) -> Dict[str, str]:
"""
Remove a stock from the watchlist.
Args:
symbol (str): Symbol of the stock to remove.
Returns:
status (str): Status of the removal operation.
"""
if not self.authenticated:
return {
"error": "User not authenticated. Please log in to modify the watchlist."
}
if symbol not in self.watch_list:
return {"error": f"Stock {symbol} not found in watchlist."}
self.watch_list.remove(symbol)
return {"status": f"Stock {symbol} removed from watchlist successfully."}
def get_watchlist(self) -> Dict[str, List[str]]:
"""
Get the watchlist.
Returns:
watchlist (List[str]): List of stock symbols in the watchlist.
"""
if not self.authenticated:
return ["Error: User not authenticated. Please log in to view the watchlist."]
if self.long_context:
watch_list = self.watch_list.copy()
watch_list.extend(WATCH_LIST_EXTENSION)
return watch_list
return {"watchlist": self.watch_list}
def get_order_history(self) -> Dict[str, List[Dict[str, Union[str, int, float]]]]:
"""
Get the stock order ID history.
Returns:
order_history (List[int]): List of orders ID in the order history.
"""
if not self.authenticated:
return [
{"error": "User not authenticated. Please log in to view order history."}
]
return {"history": list(self.orders.keys())}
def get_transaction_history(
self, start_date: Optional[str] = None, end_date: Optional[str] = None
) -> Dict[str, List[Dict[str, Union[str, float]]]]:
"""
Get the transaction history within a specified date range.
Args:
start_date (str): [Optional] Start date for the history (format: 'YYYY-MM-DD').
end_date (str): [Optional] End date for the history (format: 'YYYY-MM-DD').
Returns:
transaction_history (List[Dict]): List of transactions within the specified date range.
- type (str): Type of transaction. [Enum]: ["deposit", "withdrawal"]
- amount (float): Amount involved in the transaction.
- timestamp (str): Timestamp of the transaction, formatted as 'YYYY-MM-DD HH:MM:SS'.
"""
if not self.authenticated:
return [
{
"error": "User not authenticated. Please log in to view transaction history."
}
]
if start_date:
start = datetime.strptime(start_date, "%Y-%m-%d")
else:
start = datetime.min
if end_date:
end = datetime.strptime(end_date, "%Y-%m-%d")
else:
end = datetime.max
filtered_history = [
transaction
for transaction in self.transaction_history
if start
<= datetime.strptime(transaction["timestamp"], "%Y-%m-%d %H:%M:%S")
<= end
]
if self.long_context:
filtered_history.extend(TRANSACTION_HISTORY_EXTENSION)
return {"transaction_history": filtered_history}
# below contains a list of functions to be nested
def get_available_stocks(self, sector: str) -> Dict[str, List[str]]:
"""
Get a list of stock symbols in the given sector.
Args:
sector (str): The sector to retrieve stocks from (e.g., 'Technology').
Returns:
stock_list (List[str]): List of stock symbols in the specified sector.
"""
sector_map = {
"Technology": ["AAPL", "GOOG", "MSFT", "NVDA"],
"Automobile": ["TSLA", "F", "GM"],
}
if self.long_context:
sector_map["Technology"].extend(TECHNOLOGY_EXTENSION)
sector_map["Automobile"].extend(AUTOMOBILE_EXTENSION)
return {"stock_list": sector_map.get(sector, [])}
def filter_stocks_by_price(
self, stocks: List[str], min_price: float, max_price: float
) -> Dict[str, List[str]]:
"""
Filter stocks based on a price range.
Args:
stocks (List[str]): List of stock symbols to filter.
min_price (float): Minimum stock price.
max_price (float): Maximum stock price.
Returns:
filtered_stocks (List[str]): Filtered list of stock symbols within the price range.
"""
filtered_stocks = [
symbol
for symbol in stocks
if self.stocks.get(symbol, {}).get("price", 0) >= min_price
and self.stocks.get(symbol, {}).get("price", 0) <= max_price
]
return {"filtered_stocks": filtered_stocks}
def add_to_watchlist(self, stock: str) -> Dict[str, List[str]]:
"""
Add a stock to the watchlist.
Args:
stock (str): the stock symbol to add to the watchlist.
Returns:
watchlist (List[str]): the watchlist.
"""
if stock not in self.watch_list:
if stock in self.stocks: # Ensure symbol is valid
self.watch_list.append(stock)
return {"watchlist": self.watch_list}
def notify_price_change(self, stocks: List[str], threshold: float) -> Dict[str, str]:
"""
Notify if there is a significant price change in the stocks.
Args:
stocks (List[str]): List of stock symbols to check.
threshold (float): Percentage change threshold to trigger a notification.
Returns:
notification (str): Notification message about the price changes.
"""
changed_stocks = [
symbol
for symbol in stocks
if symbol in self.stocks
and abs(self.stocks[symbol]["percent_change"]) >= threshold
]
if changed_stocks:
return {
"notification": f"Stocks {', '.join(changed_stocks)} have significant price changes."
}
else:
return {"notification": "No significant price changes in the selected stocks."}
@@ -0,0 +1,923 @@
import random
from copy import deepcopy
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Union
from benchmarks.bfcl.executable_runtime.func_source_code.long_context import (
BOOKING_RECORD_EXTENSION,
CREDIT_CARD_EXTENSION,
)
DEFAULT_STATE = {
"random_seed": 141053,
"credit_card_list": {},
"booking_record": {},
"access_token": None,
"token_type": None,
"token_expires_in": None,
"token_scope": None,
"user_first_name": None,
"user_last_name": None,
"budget_limit": None,
}
class TravelAPI:
# Adapted from source : https://developer.concur.com/api-reference/
def __init__(self):
super().__init__()
self.credit_card_list: Dict[str, Dict[str, Union[str, int, float]]]
self.booking_record: Dict[str, Dict[str, Union[str, float]]]
self.access_token: Optional[str]
self.token_type: Optional[str]
self.token_expires_in: Optional[int]
self.token_scope: Optional[str]
self.user_first_name: Optional[str]
self.user_last_name: Optional[str]
self.budget_limit: Optional[float]
self._api_description = "This tool belongs to the travel system, which allows users to book flights, manage credit cards, and view budget information."
self._flight_cost_lookup: Dict[str, Dict[str, float]] = {}
def _load_scenario(
self,
scenario: Dict[str, Union[Dict, str, int, float]],
long_context: bool = False,
) -> None:
"""
Load a scenario from the scenarios folder
Args:
scenario (Dict[str, str]): The scenario to load
"""
DEFAULT_STATE_COPY = deepcopy(DEFAULT_STATE)
self._random = random.Random(
(scenario.get("random_seed", DEFAULT_STATE_COPY["random_seed"]))
)
self.credit_card_list = scenario.get(
"credit_card_list", DEFAULT_STATE_COPY["credit_card_list"]
)
self.booking_record = scenario.get(
"booking_record", DEFAULT_STATE_COPY["booking_record"]
)
self.access_token = scenario.get("access_token", DEFAULT_STATE_COPY["access_token"])
self.token_type = scenario.get("token_type", DEFAULT_STATE_COPY["token_type"])
self.token_expires_in = scenario.get(
"token_expires_in", DEFAULT_STATE_COPY["token_expires_in"]
)
self.token_scope = scenario.get("token_scope", DEFAULT_STATE_COPY["token_scope"])
self.user_first_name = scenario.get(
"user_first_name", DEFAULT_STATE_COPY["user_first_name"]
)
self.user_last_name = scenario.get(
"user_last_name", DEFAULT_STATE_COPY["user_last_name"]
)
self.budget_limit = scenario.get("budget_limit", DEFAULT_STATE_COPY["budget_limit"])
self.long_context = long_context
if self.long_context:
self._add_credit_cards() # Add credit card extension for long context
self._add_booking_records() # Add booking record extension
def __eq__(self, value: object) -> bool:
if not isinstance(value, TravelAPI):
return False
for attr_name in vars(self):
if attr_name.startswith("_"):
continue
model_attr = getattr(self, attr_name)
ground_truth_attr = getattr(value, attr_name)
if model_attr != ground_truth_attr:
return False
return True
def _add_credit_cards(self) -> None:
"""
Merge the credit card list with predefined credit cards from long_context.py.
Existing cards in the scenario won't be overwritten.
"""
for card_id, card_info in CREDIT_CARD_EXTENSION.items():
if card_id not in self.credit_card_list:
self.credit_card_list[card_id] = card_info
def _add_booking_records(self) -> None:
"""
Merge the booking record list with predefined booking records from long_context.py.
Existing bookings in the scenario won't be overwritten.
"""
for booking_id, booking_info in BOOKING_RECORD_EXTENSION.items():
if booking_id not in self.booking_record:
self.booking_record[booking_id] = booking_info
def _cache_flight_cost_entry(
self, travel_from, travel_to, cost, travel_class, travel_date
):
key = f"{travel_from}|{travel_to}|{travel_class}|{travel_date}"
self._flight_cost_lookup[key] = {"cost": cost}
def authenticate_travel(
self,
client_id: str,
client_secret: str,
refresh_token: str,
grant_type: str,
user_first_name: str,
user_last_name: str,
) -> Dict[str, Union[int, str]]:
"""
Authenticate the user with the travel API
Args:
client_id (str): The client applications client_id supplied by App Management
client_secret (str): The client applications client_secret supplied by App Management
refresh_token (str): The refresh token obtained from the initial authentication
grant_type (str): The grant type of the authentication request. Here are the options: read_write, read, write
user_first_name (str): The first name of the user
user_last_name (str): The last name of the user
Returns:
expires_in (int): The number of time it can use until the access token expires
access_token (str): The access token to be used in the Authorization header of future requests
token_type (str): The type of token
scope (str): The scope of the token
"""
self.token_expires_in = 2
self.access_token = str(self._random.randint(100000, 999999)) # 6 digits
self.token_type = "Bearer"
self.token_scope = grant_type
self.user_first_name = user_first_name
self.user_last_name = user_last_name
return {
"expires_in": 2,
"access_token": self.access_token,
"token_type": "Bearer",
"scope": grant_type,
}
def travel_get_login_status(self) -> Dict[str, bool]:
"""
Get the status of the login
Returns:
status (bool): The status of the login
"""
is_not_loggedin = self.token_expires_in is None or self.token_expires_in == 0
return {"status": not is_not_loggedin}
def get_budget_fiscal_year(
self,
lastModifiedAfter: Optional[str] = None,
includeRemoved: Optional[str] = None,
) -> Dict[str, str]:
"""
Get the budget fiscal year
Args:
lastModifiedAfter (str): [Optional] Use this field if you only want Fiscal Years that were changed after the supplied date. The supplied date will be interpreted in the UTC time zone. If lastModifiedAfter is not supplied, the service will return all Fiscal Years, regardless of modified date. Example: 2016-03-29T16:12:20. Return in the format of YYYY-MM-DDTHH:MM:SS.
includeRemoved (str): [Optional] If true, the service will return all Fiscal Years, including those that were previously removed. If not supplied, this field defaults to false.
Returns:
budget_fiscal_year (str): The budget fiscal year
"""
return {"budget_fiscal_year": "2018"}
def register_credit_card(
self,
access_token: str,
card_number: str,
expiration_date: str,
cardholder_name: str,
card_verification_number: int,
) -> Dict[str, Union[str, Dict[str, str]]]:
"""
Register a credit card
Args:
access_token (str): The access token obtained from the authenticate method
card_number (str): The credit card number
expiration_date (str): The expiration date of the credit card in the format MM/YYYY
cardholder_name (str): The name of the cardholder
card_verification_number (int): The card verification number
Returns:
card_id (str): The ID of the registered credit card
"""
if self.token_expires_in is None:
return {"error": "Token not initialized"}
if self.token_expires_in == 0:
return {"error": "Token expired"}
if access_token != self.access_token:
return {"error": "Invalid access token"}
if card_number in self.credit_card_list:
return {"error": "Card already registered"}
card_id = str(self._random.randint(100000000000, 999999999999)) # 12 digits
self.credit_card_list[card_id] = {
"card_number": card_number,
"expiration_date": expiration_date,
"cardholder_name": cardholder_name,
"card_verification_number": card_verification_number,
"balance": self._random.randint(10000, 99999), # 5 digits
}
return {"card_id": card_id}
def _set_card_balance(self, card_id: str, balance: float) -> None:
"""
Set the balance of a credit card
Args:
card_id (str): The ID of the credit card
balance (float): The balance of the credit card
"""
self.credit_card_list[card_id]["balance"] = balance
def get_flight_cost(
self, travel_from: str, travel_to: str, travel_date: str, travel_class: str
) -> Dict[str, List[float]]:
"""
Get the list of cost of a flight in USD based on location, date, and class
Args:
travel_from (str): The 3 letter code of the departing airport
travel_to (str): The 3 letter code of the arriving airport
travel_date (str): The date of the travel in the format 'YYYY-MM-DD'
travel_class (str): The class of the travel. Options are: economy, business, first.
Returns:
travel_cost_list (List[float]): The list of cost of the travel
"""
base_costs: Dict[Tuple[str, str], int] = {
("SFO", "LAX"): 200,
("SFO", "JFK"): 500,
("SFO", "ORD"): 400,
("SFO", "BOS"): 450,
("SFO", "RMS"): 300,
("SFO", "SBK"): 350,
("SFO", "MPC"): 370,
("SFO", "SVP"): 320,
("SFO", "SHD"): 330,
("SFO", "SSV"): 340,
("SFO", "OKD"): 360,
("SFO", "WLB"): 310,
("SFO", "CRH"): 380,
("SFO", "ATV"): 390,
("SFO", "PHV"): 420,
("SFO", "GFD"): 430,
("SFO", "CIA"): 700,
("LAX", "SFO"): 100,
("LAX", "JFK"): 600,
("LAX", "ORD"): 500,
("LAX", "BOS"): 550,
("LAX", "RMS"): 310,
("LAX", "SBK"): 320,
("LAX", "MPC"): 330,
("LAX", "SVP"): 340,
("LAX", "SHD"): 350,
("LAX", "SSV"): 360,
("LAX", "OKD"): 370,
("LAX", "WLB"): 380,
("LAX", "CRH"): 390,
("LAX", "ATV"): 400,
("LAX", "PHV"): 410,
("LAX", "GFD"): 420,
("LAX", "HND"): 430,
("JFK", "ORD"): 300,
("JFK", "BOS"): 250,
("JFK", "RMS"): 450,
("JFK", "SBK"): 460,
("JFK", "MPC"): 470,
("JFK", "SVP"): 480,
("JFK", "SHD"): 490,
("JFK", "SSV"): 500,
("JFK", "OKD"): 510,
("JFK", "WLB"): 520,
("JFK", "CRH"): 530,
("JFK", "ATV"): 540,
("JFK", "PHV"): 550,
("JFK", "GFD"): 560,
("JFK", "LAX"): 570,
("JFK", "HND"): 800,
("JFK", "PVG"): 950,
("JFK", "PEK"): 1000,
("ORD", "LAX"): 180,
("ORD", "BOS"): 200,
("ORD", "RMS"): 350,
("ORD", "SBK"): 360,
("ORD", "MPC"): 370,
("ORD", "SVP"): 380,
("ORD", "SHD"): 390,
("ORD", "SSV"): 400,
("ORD", "OKD"): 410,
("ORD", "WLB"): 420,
("ORD", "CRH"): 430,
("ORD", "ATV"): 440,
("ORD", "PHV"): 450,
("ORD", "GFD"): 460,
("BOS", "RMS"): 400,
("BOS", "SBK"): 410,
("BOS", "MPC"): 420,
("BOS", "SVP"): 430,
("BOS", "SHD"): 440,
("BOS", "SSV"): 450,
("BOS", "OKD"): 460,
("BOS", "WLB"): 470,
("BOS", "CRH"): 480,
("BOS", "ATV"): 490,
("BOS", "PHV"): 500,
("BOS", "GFD"): 510,
("RMS", "BOS"): 200,
("RMS", "JFK"): 210,
("RMS", "SBK"): 220,
("RMS", "MPC"): 230,
("RMS", "SVP"): 240,
("RMS", "SHD"): 250,
("RMS", "SSV"): 260,
("RMS", "OKD"): 270,
("RMS", "WLB"): 280,
("RMS", "CRH"): 290,
("RMS", "ATV"): 300,
("RMS", "PHV"): 310,
("RMS", "GFD"): 320,
("RMS", "LAX"): 330,
("SBK", "MPC"): 200,
("SBK", "SVP"): 210,
("SBK", "SHD"): 220,
("SBK", "SSV"): 230,
("SBK", "OKD"): 240,
("SBK", "WLB"): 250,
("SBK", "CRH"): 260,
("SBK", "ATV"): 270,
("SBK", "PHV"): 280,
("SBK", "GFD"): 290,
("MPC", "SVP"): 210,
("MPC", "SHD"): 220,
("MPC", "SSV"): 230,
("MPC", "OKD"): 240,
("MPC", "WLB"): 250,
("MPC", "CRH"): 260,
("MPC", "ATV"): 270,
("MPC", "PHV"): 280,
("MPC", "GFD"): 290,
("SVP", "SHD"): 230,
("SVP", "SSV"): 240,
("SVP", "OKD"): 250,
("SVP", "WLB"): 260,
("SVP", "CRH"): 270,
("SVP", "ATV"): 280,
("SVP", "PHV"): 290,
("SVP", "GFD"): 300,
("SHD", "SSV"): 220,
("SHD", "OKD"): 230,
("SHD", "WLB"): 240,
("SHD", "CRH"): 250,
("SHD", "ATV"): 260,
("SHD", "PHV"): 270,
("SHD", "GFD"): 280,
("SSV", "OKD"): 240,
("SSV", "WLB"): 250,
("SSV", "CRH"): 260,
("SSV", "ATV"): 270,
("SSV", "PHV"): 280,
("SSV", "GFD"): 290,
("OKD", "WLB"): 230,
("OKD", "CRH"): 240,
("OKD", "ATV"): 250,
("OKD", "PHV"): 260,
("OKD", "GFD"): 270,
("WLB", "CRH"): 250,
("WLB", "ATV"): 260,
("WLB", "PHV"): 270,
("WLB", "GFD"): 280,
("CRH", "ATV"): 240,
("CRH", "PHV"): 250,
("CRH", "GFD"): 260,
("CRH", "SFO"): 270,
("CRH", "RMS"): 280,
("CRH", "HKG"): 290,
("CRH", "JFK"): 300,
("ATV", "PHV"): 230,
("ATV", "GFD"): 240,
("PHV", "GFD"): 220,
("LHR", "CDG"): 100,
("OKD", "LAX"): 220,
}
# Ensure the travel_from and travel_to is a tuple in the correct order (from, to)
travel_pair = (travel_from, travel_to)
# Get the base cost, raise an error if the route is not available
if travel_pair in base_costs:
base_cost = base_costs[travel_pair]
else:
raise ValueError("No available route for the given airports.")
# Determine the multiplier based on the travel class
if travel_class == "economy":
factor = 1
elif travel_class == "business":
factor = 2
elif travel_class == "first":
factor = 5
else:
raise ValueError("Invalid travel class. Options are: economy, business, first.")
# Determine the multiplier based on the travel date
digit_sum = sum(int(char) for char in travel_date if char.isdigit())
travel_date_multiplier = 2 if digit_sum % 2 == 0 else 1
# Calculate the total cost
travel_cost = float(base_cost * factor * travel_date_multiplier)
travel_cost_list = []
if self.long_context:
self._flight_cost_lookup = {} # reset cache
for (frm, to), base in base_costs.items():
cost = float(base * factor * travel_date_multiplier)
self._cache_flight_cost_entry(frm, to, cost, travel_class, travel_date)
travel_cost_list.append(cost)
else:
cost = float(base_costs[travel_pair] * factor * travel_date_multiplier)
travel_cost_list = [cost]
self._flight_cost_lookup = {
f"{travel_from}|{travel_to}|{travel_class}|{travel_date}": {"cost": cost}
}
return {"travel_cost_list": travel_cost_list}
def get_credit_card_balance(
self, access_token: str, card_id: str
) -> Dict[str, Union[float, str]]:
"""
Get the balance of a credit card
Args:
access_token (str): The access token obtained from the authenticate
card_id (str): The ID of the credit card
Returns:
card_balance (float): The balance of the credit card
"""
if self.token_expires_in == 0:
return {"error": "Token expired"}
if access_token != self.access_token:
return {"error": "Invalid access token"}
if card_id not in self.credit_card_list:
return {
"error": "Card not registered. Here are a list of card_id's: "
+ str(list(self.credit_card_list.keys()))
}
return {"card_balance": self.credit_card_list[card_id]["balance"]}
def book_flight(
self,
access_token: str,
card_id: str,
travel_date: str,
travel_from: str,
travel_to: str,
travel_class: str,
) -> Dict[str, Union[str, bool, Dict]]:
"""
Book a flight given the travel information. From and To should be the airport codes in the IATA format.
Args:
access_token (str): The access token obtained from the authenticate
card_id (str): The ID of the credit card to use for the booking
travel_date (str): The date of the travel in the format YYYY-MM-DD
travel_from (str): The location the travel is from
travel_to (str): The location the travel is to
travel_class (str): The class of the travel
Returns:
booking_id (str): The ID of the booking
transaction_id (str): The ID of the transaction
booking_status (bool): The status of the booking, True if successful, False if failed
booking_history (Dict): The booking history. This field might be empty even though the history is non-empty.
- booking_id (str): The ID of the booking
- transaction_id (str): The ID of the transaction
- travel_date (str): The date of the travel
- travel_from (str): The location the travel is from
- travel_to (str): The location the travel is to
- travel_class (str): The class of the travel
- travel_cost (float): The cost of the travel
"""
if self.token_expires_in == 0:
return {"booking_status": False, "error": "Token expired"}
if access_token != self.access_token:
return {"booking_status": False, "error": "Invalid access token"}
if card_id not in self.credit_card_list:
return {"booking_status": False, "error": "Card not registered"}
if "balance" not in self.credit_card_list[card_id]:
return {"booking_status": False, "error": "Balance not found"}
all_airports = self.list_all_airports()
if travel_from not in all_airports:
return {
"booking_status": False,
"error": f"Invalid departure airport code: {travel_from}",
}
if travel_to not in all_airports:
return {
"booking_status": False,
"error": f"Invalid destination airport code: {travel_to}",
}
try:
datetime.strptime(travel_date, "%Y-%m-%d")
except ValueError:
return {
"booking_status": False,
"error": "Invalid date format. Use YYYY-MM-DD.",
}
valid_classes = {"economy", "business", "first"}
if travel_class not in valid_classes:
return {
"booking_status": False,
"error": f"Invalid travel class. Must be one of {valid_classes}",
}
try:
self.get_flight_cost(
travel_from=travel_from,
travel_to=travel_to,
travel_date=travel_date,
travel_class=travel_class,
)
key = f"{travel_from}|{travel_to}|{travel_class}|{travel_date}"
travel_cost_entry = self._flight_cost_lookup.get(key)
if travel_cost_entry is None:
return {
"booking_status": False,
"error": "No available route for the given parameters",
}
travel_cost = travel_cost_entry["cost"]
except ValueError as e:
return {"booking_status": False, "error": str(e)}
if self.credit_card_list[card_id]["balance"] < travel_cost:
return {"booking_status": False, "error": "Insufficient funds"}
if (
self.budget_limit is not None
and self.credit_card_list[card_id]["balance"] < self.budget_limit
):
return {
"booking_status": False,
"error": "Balance is less than budget limit",
}
self.credit_card_list[card_id]["balance"] -= travel_cost
booking_id = str(self._random.randint(1000000, 9999999)) # 7 digits
transaction_id = str(self._random.randint(10000000, 99999999)) # 8 digits
self.booking_record[booking_id] = {
"card_id": card_id,
"travel_date": travel_date,
"travel_from": travel_from,
"travel_to": travel_to,
"travel_class": travel_class,
"travel_cost": travel_cost,
"transaction_id": transaction_id,
}
if self.long_context:
return {
"booking_id": booking_id,
"transaction_id": transaction_id,
"booking_status": True,
"booking_history": self.booking_record,
}
return {
"booking_id": booking_id,
"transaction_id": transaction_id,
"booking_status": True,
"booking_history": {},
}
def retrieve_invoice(
self,
access_token: str,
booking_id: Optional[str] = None,
insurance_id: Optional[str] = None,
) -> Dict[str, Union[Dict[str, Union[str, float]], str]]:
"""
Retrieve the invoice for a booking.
Args:
access_token (str): The access token obtained from the authenticate
booking_id (str): [Optional] The ID of the booking
insurance_id (str): [Optional] The ID of the insurance
Returns:
invoice (Dict): The invoice for the booking
- booking_id (str): The ID of the booking
- travel_date (str): The date of the travel
- travel_from (str): The location the travel is from
- travel_to (str): The location the travel is to
- travel_class (str): The class of the travel
- travel_cost (float): The cost of the travel
- transaction_id (str): The ID of the transaction
"""
if self.token_expires_in == 0:
return {"error": "Token expired"}
if access_token != self.access_token:
return {"error": "Invalid access token"}
if booking_id not in self.booking_record:
return {"error": "Booking not found"}
invoice = {
"booking_id": booking_id,
"travel_date": self.booking_record[booking_id]["travel_date"],
"travel_from": self.booking_record[booking_id]["travel_from"],
"travel_to": self.booking_record[booking_id]["travel_to"],
"travel_class": self.booking_record[booking_id]["travel_class"],
"travel_cost": self.booking_record[booking_id]["travel_cost"],
"transaction_id": self.booking_record[booking_id]["transaction_id"],
}
return {"invoice": invoice}
def get_booking_history(
self,
access_token: str,
) -> Dict[str, Dict[str, Dict[str, Union[str, float]]]]:
"""
Retrieve all booking history for the user.
Args:
access_token (str): The access token obtained from the authenticate method.
Returns:
booking_history (Dict): A dictionary keyed by booking_id where each value contains the booking details.
- transaction_id (str): The ID of the transaction
- travel_date (str): The date of the travel
- travel_from (str): The location the travel is from
- travel_to (str): The location the travel is to
- travel_class (str): The class of the travel
- travel_cost (float): The cost of the travel
"""
# Validate token state similar to other secured endpoints
if self.token_expires_in == 0:
return {"error": "Token expired"}
if access_token != self.access_token:
return {"error": "Invalid access token"}
# Simply return a copy of the booking records to avoid accidental mutation
return {"booking_history": deepcopy(self.booking_record)}
def list_all_airports(self) -> List[str]:
"""
List all available airports
Returns:
airports (List[str]): A list of all available airports
"""
return [
"RMS",
"SBK",
"MPC",
"SVP",
"SHD",
"CDG",
"LHR",
"SSV",
"OKD",
"WLB",
"PEK",
"HND",
"HKG",
"CIA",
"CRH",
"ATV",
"PHV",
"GFD",
"SFO",
"LAX",
"JFK",
"ORD",
"BOS",
]
def cancel_booking(
self, access_token: str, booking_id: str
) -> Dict[str, Union[bool, str]]:
"""
Cancel a booking
Args:
access_token (str): The access token obtained from the authenticate
booking_id (str): The ID of the booking
Returns:
cancel_status (bool): The status of the cancellation, True if successful, False if failed
"""
if self.token_expires_in == 0:
return {"cancel_status": False, "error": "Token expired"}
if access_token != self.access_token:
return {"cancel_status": False, "error": "Invalid access token"}
if booking_id not in self.booking_record:
return {"cancel_status": False, "error": "Booking not found"}
card_id = self.booking_record[booking_id]["card_id"]
travel_cost = self.booking_record[booking_id]["travel_cost"]
self.credit_card_list[card_id]["balance"] += travel_cost
del self.booking_record[booking_id]
return {"cancel_status": True}
def compute_exchange_rate(
self, base_currency: str, target_currency: str, value: float
) -> float:
"""
Compute the exchange rate between two currencies
Args:
base_currency (str): The base currency. [Enum]: USD, RMB, EUR, JPY, GBP, CAD, AUD, INR, RUB, BRL, MXN
target_currency (str): The target currency. [Enum]: USD, RMB, EUR, JPY, GBP, CAD, AUD, INR, RUB, BRL, MXN
value (float): The value to convert
Returns:
exchanged_value (float): The value after the exchange
"""
exchange_rates = {
("USD", "RMB"): 7,
("USD", "EUR"): 0.8,
("USD", "JPY"): 110,
("USD", "GBP"): 0.7,
("USD", "CAD"): 1.3,
("USD", "AUD"): 1.4,
("USD", "INR"): 70,
("USD", "RUB"): 60,
("USD", "BRL"): 3.8,
("USD", "MXN"): 20,
}
for key, val in exchange_rates.items():
if base_currency == key[0] and target_currency == key[1]:
return {"exchanged_value": float(value * val)}
elif base_currency == key[1] and target_currency == key[0]:
return {"exchanged_value": round(value / val, 2)}
raise ValueError("No available exchange rate for the given currencies.")
def verify_traveler_information(
self, first_name: str, last_name: str, date_of_birth: str, passport_number: str
) -> Dict[str, Union[bool, str]]:
"""
Verify the traveler information
Args:
first_name (str): The first name of the traveler
last_name (str): The last name of the traveler
date_of_birth (str): The date of birth of the traveler in the format YYYY-MM-DD
passport_number (str): The passport number of the traveler
Returns:
verification_status (bool): The status of the verification, True if successful, False if failed
verification_failure (str): The reason for the verification failure
"""
if self.user_first_name != first_name or self.user_last_name != last_name:
return {
"verification_status": False,
"verification_failure": "Cannot book flight information for another user."
+ f"Expected {self.user_first_name} {self.user_last_name}, got {first_name} {last_name}",
}
# Calculate age
try:
birth_date = datetime.strptime(date_of_birth, "%Y-%m-%d")
today = datetime.today()
age = (
today.year
- birth_date.year
- ((today.month, today.day) < (birth_date.month, birth_date.day))
)
except ValueError:
return {
"verification_status": False,
"verification_failure": "Invalid date of birth format. Please use YYYY-MM-DD.",
}
# Check if the traveler is at least 18 years old
if age < 18:
return {
"verification_status": False,
"verification_failure": "Traveler must be at least 18 years old.",
}
# Check if the passport number starts with 'US' (assuming this indicates a US passport)
if not passport_number.startswith("US"):
return {
"verification_status": False,
"verification_failure": "Passport must be issued by the United States.",
}
# If all checks pass
return {"verification_status": True}
def set_budget_limit(
self, access_token: str, budget_limit: float
) -> Dict[str, Union[float, str]]:
"""
Set the budget limit for the user
Args:
access_token (str): The access token obtained from the authentication process or initial configuration.
budget_limit (float): The budget limit to set in USD
Returns:
budget_limit (float): The budget limit set in USD
"""
if self.token_expires_in == 0:
return {"error": "Token expired"}
if access_token != self.access_token:
return {"error": "Invalid access token"}
budget_limit = float(budget_limit)
self.budget_limit = budget_limit
return {"budget_limit": budget_limit}
def get_nearest_airport_by_city(self, location: str) -> Dict[str, str]:
"""
Get the nearest airport to the given location
Args:
location (str): The name of the location. [Enum]: Rivermist, Stonebrook, Maplecrest, Silverpine, Shadowridge, London, Paris, Sunset Valley, Oakendale, Willowbend, Crescent Hollow, Autumnville, Pinehaven, Greenfield, San Francisco, Los Angeles, New York, Chicago, Boston, Beijing, Hong Kong, Rome, Tokyo
Returns:
nearest_airport (str): The nearest airport to the given location
"""
airport_map = {
"Rivermist": "RMS",
"Stonebrook": "SBK",
"Maplecrest": "MPC",
"Silverpine": "SVP",
"Shadowridge": "SHD",
"London": "LHR",
"Paris": "CDG",
"Sunset Valley": "SSV",
"Oakendale": "OKD",
"Willowbend": "WLB",
"Crescent Hollow": "CRH",
"Autumnville": "ATV",
"Pinehaven": "PHV",
"Greenfield": "GFD",
"San Francisco": "SFO",
"Los Angeles": "LAX",
"New York": "JFK",
"Chicago": "ORD",
"Boston": "BOS",
"Beijing": "PEK",
"Hong Kong": "HKG",
"Rome": "CIA",
"Tokyo": "HND",
}
return {"nearest_airport": airport_map.get(location, "Unknown")}
def purchase_insurance(
self,
access_token: str,
insurance_type: str,
booking_id: str,
insurance_cost: float,
card_id: str,
) -> Dict[str, Union[str, bool]]:
"""
Purchase insurance
Args:
access_token (str): The access token obtained from the authenticate
insurance_type (str): The type of insurance to purchase
insurance_cost (float): The cost of the insurance
booking_id (str): The ID of the booking
card_id (str): The ID of the credit card to use for the
Returns:
insurance_id (str): The ID of the insurance
insurance_status (bool): The status of the insurance purchase, True if successful, False if failed
"""
if self.token_expires_in == 0:
return {"insurance_status": False, "error": "Token expired"}
if access_token != self.access_token:
return {"insurance_status": False, "error": "Invalid access token"}
if self.budget_limit is not None and self.budget_limit < insurance_cost:
return {"insurance_status": False, "error": "Exceeded budget limit"}
if booking_id not in self.booking_record:
return {"insurance_status": False, "error": "Booking not found"}
if card_id not in self.credit_card_list:
return {"insurance_status": False, "error": "Credit card not registered"}
self.credit_card_list[card_id]["balance"] -= insurance_cost
return {
"insurance_id": str(self._random.randint(100000000, 999999999)), # 9 digits
"insurance_status": True,
}
def contact_customer_support(self, booking_id: str, message: str) -> Dict[str, str]:
"""
Contact travel booking customer support, get immediate support on an issue with an online call.
Args:
booking_id (str): The ID of the booking
message (str): The message to send to customer support
Returns:
customer_support_message (str): The message from customer support
"""
if booking_id not in self.booking_record:
return {"error": "Booking not found"}
return {
"customer_support_message": "Thank you for contacting customer support. Your message has been received and we will get back to you shortly."
}
def get_all_credit_cards(self) -> Dict[str, Dict[str, Union[str, int, float]]]:
"""
Get all registered credit cards
Returns:
credit_card_list (Dict): A dictionary containing all registered credit cards
- card_number (str): The number of the credit card
- expiration_date (str): The expiration date of the credit card in the format YYYY-MM-DD
- cardholder_name (str): The name of the cardholder
- card_verification_value (int): The verification value of the credit card
- balance (float): The balance of the credit card
"""
return {"credit_card_list": self.credit_card_list}
@@ -0,0 +1,703 @@
import random
from copy import deepcopy
from typing import Dict, List, Union
from benchmarks.bfcl.executable_runtime.func_source_code.long_context import (
CAR_STATUS_METADATA_EXTENSION,
INTERMEDIARY_CITIES,
LONG_WEATHER_EXTENSION,
PARKING_BRAKE_INSTRUCTION,
)
MAX_FUEL_LEVEL = 50
MIN_FUEL_LEVEL = 0.0
MILE_PER_GALLON = 20.0
MAX_BATTERY_VOLTAGE = 14.0
MIN_BATTERY_VOLTAGE = 10.0
DEFAULT_STATE = {
"random_seed": 141053,
"fuelLevel": 0.0,
"batteryVoltage": 12.6,
"engine_state": "stopped",
"remainingUnlockedDoors": 4,
"doorStatus": {
"driver": "unlocked",
"passenger": "unlocked",
"rear_left": "unlocked",
"rear_right": "unlocked",
},
"acTemperature": 25.0,
"fanSpeed": 50,
"acMode": "auto",
"humidityLevel": 50.0,
"headLightStatus": "off",
"parkingBrakeStatus": "released",
"_parkingBrakeForce": 0.0,
"_slopeAngle": 0.0,
"brakePedalStatus": "released",
"brakePedalForce": 0.0,
"distanceToNextVehicle": 50.0,
"cruiseStatus": "inactive",
"destination": "None",
"frontLeftTirePressure": 32.0,
"frontRightTirePressure": 32.0,
"rearLeftTirePressure": 30.0,
"rearRightTirePressure": 30.0,
}
class VehicleControlAPI:
def __init__(self):
"""
Initializes the vehicle control API with default values.
"""
self.fuelLevel: float
self.batteryVoltage: float
self.engine_state: str
self.remainingUnlockedDoors: int
self.doorStatus: Dict[str, str]
self.acTemperature: float
self.fanSpeed: int
self.acMode: str
self.humidityLevel: float
self.headLightStatus: str
self.parkingBrakeStatus: str
self._parkingBrakeForce: float
self._slopeAngle: float
self.brakePedalStatus: str
self._brakePedalForce: float
self.distanceToNextVehicle: float
self.cruiseStatus: str
self.destination: str
self.frontLeftTirePressure: float
self.frontRightTirePressure: float
self.rearLeftTirePressure: float
self.rearRightTirePressure: float
self._api_description = "This tool belongs to the vehicle control system, which allows users to control various aspects of the car such as engine, doors, climate control, lights, and more."
def _load_scenario(self, scenario: dict, long_context=False) -> None:
"""
Loads the scenario for the vehicle control.
Args:
scenario (Dict): The scenario to load.
"""
DEFAULT_STATE_COPY = deepcopy(DEFAULT_STATE)
self._random = random.Random(
(scenario.get("random_seed", DEFAULT_STATE_COPY["random_seed"]))
)
self.fuelLevel = scenario.get(
"fuelLevel", DEFAULT_STATE_COPY["fuelLevel"]
) # in gallons
self.batteryVoltage = scenario.get(
"batteryVoltage", DEFAULT_STATE_COPY["batteryVoltage"]
) # in volts
self.engine_state = scenario.get(
"engineState", DEFAULT_STATE_COPY["engine_state"]
) # running, stopped
self.remainingUnlockedDoors = scenario.get(
"remainingUnlockedDoors", DEFAULT_STATE_COPY["remainingUnlockedDoors"]
) # driver, passenger, rear_left, rear_right
self.doorStatus = scenario.get(
"doorStatus",
DEFAULT_STATE_COPY["doorStatus"],
)
self.remainingUnlockedDoors = 4 - len(
[1 for door in self.doorStatus.keys() if self.doorStatus[door] == "locked"]
)
self.acTemperature = scenario.get(
"acTemperature", DEFAULT_STATE_COPY["acTemperature"]
) # in degree Celsius
self.fanSpeed = scenario.get("fanSpeed", DEFAULT_STATE_COPY["fanSpeed"]) # 0 to 100
self.acMode = scenario.get(
"acMode", DEFAULT_STATE_COPY["acMode"]
) # auto, cool, heat, defrost
self.humidityLevel = scenario.get(
"humidityLevel", DEFAULT_STATE_COPY["humidityLevel"]
) # in percentage
self.headLightStatus = scenario.get(
"headLightStatus", DEFAULT_STATE_COPY["headLightStatus"]
) # on, off
self.parkingBrakeStatus = scenario.get(
"parkingBrakeStatus", DEFAULT_STATE_COPY["parkingBrakeStatus"]
) # released, engaged
self._parkingBrakeForce = scenario.get(
"parkingBrakeForce", DEFAULT_STATE_COPY["_parkingBrakeForce"]
) # in Newtons
self._slopeAngle = scenario.get(
"slopeAngle", DEFAULT_STATE_COPY["_slopeAngle"]
) # in degrees
self.brakePedalStatus = scenario.get(
"brakePedalStatus", DEFAULT_STATE_COPY["brakePedalStatus"]
) # pressed, released
self._brakePedalForce = scenario.get(
"brakePedalForce", DEFAULT_STATE_COPY["brakePedalForce"]
) # in Newtons
self.distanceToNextVehicle = scenario.get(
"distanceToNextVehicle", DEFAULT_STATE_COPY["distanceToNextVehicle"]
) # in meters
self.cruiseStatus = scenario.get(
"cruiseStatus", DEFAULT_STATE_COPY["cruiseStatus"]
) # active, inactive
self.destination = scenario.get("destination", DEFAULT_STATE_COPY["destination"])
self.frontLeftTirePressure = scenario.get(
"frontLeftTirePressure", DEFAULT_STATE_COPY["frontLeftTirePressure"]
)
self.frontRightTirePressure = scenario.get(
"frontRightTirePressure", DEFAULT_STATE_COPY["frontRightTirePressure"]
)
self.rearLeftTirePressure = scenario.get(
"rearLeftTirePressure", DEFAULT_STATE_COPY["rearLeftTirePressure"]
)
self.rearRightTirePressure = scenario.get(
"rearRightTirePressure", DEFAULT_STATE_COPY["rearRightTirePressure"]
)
self.long_context = long_context
def __eq__(self, value: object) -> bool:
if not isinstance(value, VehicleControlAPI):
return False
for attr_name in vars(self):
if attr_name.startswith("_"):
continue
model_attr = getattr(self, attr_name)
ground_truth_attr = getattr(value, attr_name)
if model_attr != ground_truth_attr:
return False
return True
def startEngine(self, ignitionMode: str) -> Dict[str, Union[str, float]]:
"""
Starts the engine of the vehicle.
Args:
ignitionMode (str): The ignition mode of the vehicle. [Enum]: ["START", "STOP"]
Returns:
engineState (str): The state of the engine. [Enum]: ["running", "stopped"]
fuelLevel (float): The fuel level of the vehicle in gallons.
batteryVoltage (float): The battery voltage of the vehicle in volts.
"""
if ignitionMode == "STOP":
self.engine_state = "stopped"
if self.remainingUnlockedDoors > 0:
return {
"error": "All doors must be locked before starting the engine. Here are the unlocked doors: "
+ ", ".join(
[
door
for door, status in self.doorStatus.items()
if status == "unlocked"
]
)
}
if self.brakePedalStatus != "pressed":
return {"error": "Brake pedal needs to be pressed when starting the engine."}
if self._brakePedalForce != 1000.0:
return {"error": "Must press the brake fully before starting the engine."}
if self.fuelLevel < MIN_FUEL_LEVEL:
return {"error": "Fuel tank is empty."}
if ignitionMode == "START":
self.engine_state = "running"
else:
return {"error": "Invalid ignition mode."}
return {
"engineState": self.engine_state,
"fuelLevel": self.fuelLevel,
"batteryVoltage": self.batteryVoltage,
}
def fillFuelTank(self, fuelAmount: float) -> Dict[str, Union[str, float]]:
"""
Fills the fuel tank of the vehicle. The fuel tank can hold up to 50 gallons.
Args:
fuelAmount (float): The amount of fuel to fill in gallons; this is the additional fuel to add to the tank.
Returns:
fuelLevel (float): The fuel level of the vehicle in gallons.
"""
if fuelAmount < 0:
return {"error": "Fuel amount cannot be negative."}
if self.fuelLevel + fuelAmount > MAX_FUEL_LEVEL:
return {"error": "Cannot fill gas above the tank capacity."}
if self.fuelLevel + fuelAmount < MIN_FUEL_LEVEL:
return {"error": "Fuel tank is empty. Min fuel level is 0 gallons."}
self.fuelLevel += fuelAmount
return {"fuelLevel": self.fuelLevel}
def lockDoors(self, unlock: bool, door: list[str]) -> Dict[str, Union[str, int]]:
"""
Locks the doors of the vehicle.
Args:
unlock (bool): True if the doors are to be unlocked, False otherwise.
door (List[str]): The list of doors to lock or unlock. [Enum]: ["driver", "passenger", "rear_left", "rear_right"]
Returns:
lockStatus (str): The status of the lock. [Enum]: ["locked", "unlocked"]
remainingUnlockedDoors (int): The number of remaining unlocked doors.
"""
if unlock:
for d in door:
if self.doorStatus[d] == "unlocked":
continue
self.doorStatus[d] = "unlocked"
self.remainingUnlockedDoors += 1
return {
"lockStatus": "unlocked",
"remainingUnlockedDoors": self.remainingUnlockedDoors,
}
else:
for d in door:
if self.doorStatus[d] == "locked":
continue
self.doorStatus[d] = "locked"
self.remainingUnlockedDoors -= 1
return {
"lockStatus": "locked",
"remainingUnlockedDoors": self.remainingUnlockedDoors,
}
def adjustClimateControl(
self,
temperature: float,
unit: str = "celsius",
fanSpeed: int = 50,
mode: str = "auto",
) -> Dict[str, Union[str, float]]:
"""
Adjusts the climate control of the vehicle.
Args:
temperature (float): The temperature to set in degree. Default to be celsius.
unit (str): [Optional] The unit of temperature. [Enum]: ["celsius", "fahrenheit"]
fanSpeed (int): [Optional] The fan speed to set from 0 to 100. Default is 50.
mode (str): [Optional] The climate mode to set. [Enum]: ["auto", "cool", "heat", "defrost"]
Returns:
currentTemperature (float): The current temperature set in degree Celsius.
climateMode (str): The current climate mode set.
humidityLevel (float): The humidity level in percentage.
"""
if not (0 <= fanSpeed <= 100):
return {"error": "Fan speed must be between 0 and 100."}
self.acTemperature = temperature
if unit == "fahrenheit":
self.acTemperature = (temperature - 32) * 5 / 9
self.fanSpeed = fanSpeed
self.acMode = mode
return {
"currentACTemperature": temperature,
"climateMode": mode,
"humidityLevel": self.humidityLevel,
}
def get_outside_temperature_from_google(self) -> Dict[str, float]:
"""
Gets the outside temperature.
Returns:
outsideTemperature (float): The outside temperature in degree Celsius.
"""
if self.long_context:
LONG_WEATHER_EXTENSION["outsideTemperature"] = self._random.uniform(-10.0, 40.0)
return LONG_WEATHER_EXTENSION
return {"outsideTemperature": self._random.uniform(-10.0, 40.0)}
def get_outside_temperature_from_weather_com(self) -> Dict[str, float]:
"""
Gets the outside temperature.
Returns:
outsideTemperature (float): The outside temperature in degree Celsius.
"""
return {"error": 404}
def setHeadlights(self, mode: str) -> Dict[str, str]:
"""
Sets the headlights of the vehicle.
Args:
mode (str): The mode of the headlights. [Enum]: ["on", "off", "auto"]
Returns:
headlightStatus (str): The status of the headlights. [Enum]: ["on", "off"]
"""
if mode not in ["on", "off", "auto"]:
return {"error": "Invalid headlight mode."}
if mode == "on":
self.headLightStatus = "on"
return {"headlightStatus": "on"}
else:
self.headLightStatus = "off"
return {"headlightStatus": "off"}
def displayCarStatus(self, option: str) -> Dict[str, Union[str, float, Dict[str, str]]]:
"""
Displays the status of the vehicle based on the provided display option.
Args:
option (str): The option to display. [Enum]: ["fuel", "battery", "doors", "climate", "headlights", "parkingBrake", "brakePedal", "engine"]
Returns:
status (Dict): The status of the vehicle based on the option.
- fuelLevel (float): [Optional] The fuel level of the vehicle in gallons.
- batteryVoltage (float): [Optional] The battery voltage of the vehicle in volts.
- doorStatus (Dict): [Optional] The status of the doors.
- driver (str): The status of the driver door. [Enum]: ["locked", "unlocked"]
- passenger (str): The status of the passenger door. [Enum]: ["locked", "unlocked"]
- rear_left (str): The status of the rear left door. [Enum]: ["locked", "unlocked"]
- rear_right (str): The status of the rear right door. [Enum]: ["locked", "unlocked"]
- currentACTemperature (float): [Optional] The current temperature set in degree Celsius.
- fanSpeed (int): [Optional] The fan speed set from 0 to 100.
- climateMode (str): [Optional] The climate mode set. [Enum]: ["auto", "cool", "heat", "defrost"]
- humidityLevel (float): [Optional] The humidity level in percentage.
- headlightStatus (str): [Optional] The status of the headlights. [Enum]: ["on", "off"]
- parkingBrakeStatus (str): [Optional] The status of the brake. [Enum]: ["engaged", "released"]
- parkingBrakeForce (float): [Optional] The force applied to the brake in Newtons.
- slopeAngle (float): [Optional] The slope angle in degrees.
- brakePedalStatus (str): [Optional] The status of the brake pedal. [Enum]: ["pressed", "released"]
- brakePedalForce (float): [Optional] The force applied to the brake pedal in Newtons.
- engineState (str): [Optional] The state of the engine. [Enum]: ["running", "stopped"]
- metadata (str): [Optional] The metadata of the car.
"""
status = {}
if self.long_context:
status["metadata"] = CAR_STATUS_METADATA_EXTENSION
if option == "fuel":
status["fuelLevel"] = self.fuelLevel
elif option == "battery":
status["batteryVoltage"] = self.batteryVoltage
elif option == "doors":
status["doorStatus"] = self.doorStatus
elif option == "climate":
status["currentACTemperature"] = self.acTemperature
status["fanSpeed"] = self.fanSpeed
status["climateMode"] = self.acMode
status["humidityLevel"] = self.humidityLevel
elif option == "headlights":
status["headlightStatus"] = self.headLightStatus
elif option == "parkingBrake":
status["parkingBrakeStatus"] = self.parkingBrakeStatus
status["parkingBrakeForce"] = self._parkingBrakeForce
status["slopeAngle"] = self._slopeAngle
elif option == "brakePedal":
status["brakePedalStatus"] = self.brakePedalStatus
status["brakePedalForce"] = self._brakePedalForce
elif option == "engine":
status["engineState"] = self.engine_state
else:
status["error"] = "Invalid option"
return status
def activateParkingBrake(self, mode: str) -> Dict[str, Union[str, float]]:
"""
Activates the parking brake of the vehicle.
Args:
mode (str): The mode to set. [Enum]: ["engage", "release"]
Returns:
parkingBrakeStatus (str): The status of the brake. [Enum]: ["engaged", "released"]
_parkingBrakeForce (float): The force applied to the brake in Newtons.
_slopeAngle (float): The slope angle in degrees.
"""
if mode not in ["engage", "release"]:
return {"error": "Invalid mode"}
if mode == "engage":
self.parkingBrakeStatus = "engaged"
self._parkingBrakeForce = 500.0
self._slopeAngle = 10.0
if self.long_context:
return {
"parkingBrakeInstruction": PARKING_BRAKE_INSTRUCTION,
"parkingBrakeStatus": "engaged",
"_parkingBrakeForce": 500.0,
"_slopeAngle": 10.0,
}
return {"parkingBrakeStatus": "engaged", "_parkingBrakeForce": 500.0, "_slopeAngle": 10.0}
else:
self.parkingBrakeStatus = "released"
self._parkingBrakeForce = 0.0
self._slopeAngle = 10.0
if self.long_context:
return {
"parkingBrakeInstruction": PARKING_BRAKE_INSTRUCTION,
"parkingBrakeStatus": "released",
"_parkingBrakeForce": 0.0,
"_slopeAngle": 10.0,
}
return {"parkingBrakeStatus": "released", "_parkingBrakeForce": 0.0, "_slopeAngle": 10.0}
def pressBrakePedal(self, pedalPosition: float) -> Dict[str, Union[str, float]]:
"""
Presses the brake pedal based on pedal position. The brake pedal will be kept pressed until released.
Args:
pedalPosition (float): Position of the brake pedal, between 0 (not pressed) and 1 (fully pressed).
Returns:
brakePedalStatus (str): The status of the brake pedal. [Enum]: ["pressed", "released"]
brakePedalForce (float): The force applied to the brake pedal in Newtons.
"""
# Validate pedal position is within 0 to 1
if not (0 <= pedalPosition <= 1):
return {"error": "Pedal position must be between 0 and 1."}
# Release the brake if pedal position is zero
if pedalPosition == 0:
self.brakePedalStatus = "released"
self._brakePedalForce = 0.0
return {"brakePedalStatus": "released", "brakePedalForce": 0.0}
# Calculate force based on pedal position
max_brake_force = 1000 # Max force in Newtons
force = pedalPosition * max_brake_force
# Update the brake pedal status and force
self.brakePedalStatus = "pressed"
self._brakePedalForce = force
return {"brakePedalStatus": "pressed", "brakePedalForce": float(force)}
def releaseBrakePedal(self) -> Dict[str, Union[str, float]]:
"""
Releases the brake pedal of the vehicle.
Returns:
brakePedalStatus (str): The status of the brake pedal. [Enum]: ["pressed", "released"]
brakePedalForce (float): The force applied to the brake pedal in Newtons.
"""
self.brakePedalStatus = "released"
self._brakePedalForce = 0.0
return {"brakePedalStatus": "released", "brakePedalForce": 0.0}
def setCruiseControl(
self, speed: float, activate: bool, distanceToNextVehicle: float
) -> Dict[str, Union[str, float]]:
"""
Sets the cruise control of the vehicle.
Args:
speed (float): The speed to set in m/h. The speed should be between 0 and 120 and a multiple of 5.
activate (bool): True to activate the cruise control, False to deactivate.
distanceToNextVehicle (float): The distance to the next vehicle in meters.
Returns:
cruiseStatus (str): The status of the cruise control. [Enum]: ["active", "inactive"]
currentSpeed (float): The current speed of the vehicle in km/h.
distanceToNextVehicle (float): The distance to the next vehicle in meters.
"""
distanceToNextVehicle = float(distanceToNextVehicle)
speed = float(speed)
if self.engine_state == "stopped":
return {"error": "Start the engine before activating the cruise control."}
if activate:
self.distanceToNextVehicle = distanceToNextVehicle
if speed < 0 or speed > 120 or speed % 5 != 0:
return {"error": "Invalid speed"}
self.cruiseStatus = "active"
return {
"cruiseStatus": "active",
"currentSpeed": speed,
"distanceToNextVehicle": distanceToNextVehicle,
}
else:
self.cruiseStatus = "inactive"
self.distanceToNextVehicle = distanceToNextVehicle
return {
"cruiseStatus": "inactive",
"currentSpeed": speed,
"distanceToNextVehicle": distanceToNextVehicle,
}
def get_current_speed(self) -> Dict[str, float]:
"""
Gets the current speed of the vehicle.
Returns:
currentSpeed (float): The current speed of the vehicle in km/h.
"""
return {"currentSpeed": self._random.uniform(0.0, 120.0)}
def display_log(self, messages: List[str]):
"""
Displays the log messages.
Args:
messages (List[str]): The list of messages to display.
Returns:
log (List[str]): The list of messages displayed.
"""
return {"log": messages}
def estimate_drive_feasibility_by_mileage(self, distance: float) -> Dict[str, bool]:
"""
Estimates the milage of the vehicle given the distance needed to drive.
Args:
distance (float): The distance to travel in miles.
Returns:
canDrive (bool): True if the vehicle can drive the distance, False otherwise.
"""
if self.fuelLevel * MILE_PER_GALLON < distance:
return {"canDrive": False}
else:
return {"canDrive": True}
def liter_to_gallon(self, liter: float) -> Dict[str, float]:
"""
Converts the liter to gallon.
Args:
liter (float): The amount of liter to convert.
Returns:
gallon (float): The amount of gallon converted.
"""
return {"gallon": liter * 0.264172}
def gallon_to_liter(self, gallon: float) -> Dict[str, float]:
"""
Converts the gallon to liter.
Args:
gallon (float): The amount of gallon to convert.
Returns:
liter (float): The amount of liter converted.
"""
return {"liter": gallon * 3.78541}
def estimate_distance(self, cityA: str, cityB: str) -> Dict[str, float]:
"""
Estimates the distance between two cities.
Args:
cityA (str): The zipcode of the first city.
cityB (str): The zipcode of the second city.
Returns:
distance (float): The distance between the two cities in km.
intermediaryCities (List[str]): [Optional] The list of intermediary cities between the two cities.
"""
if (cityA == "83214" and cityB == "74532") or (
cityA == "74532" and cityB == "83214"
):
distance = {"distance": 750.0}
elif (cityA == "56108" and cityB == "62947") or (
cityA == "62947" and cityB == "56108"
):
distance = {"distance": 320.0}
elif (cityA == "71354" and cityB == "83462") or (
cityA == "83462" and cityB == "71354"
):
distance = {"distance": 450.0}
elif (cityA == "47329" and cityB == "52013") or (
cityA == "52013" and cityB == "47329"
):
distance = {"distance": 290.0}
elif (cityA == "69238" and cityB == "51479") or (
cityA == "51479" and cityB == "69238"
):
distance = {"distance": 630.0}
elif (cityA == "94016" and cityB == "83214") or (
cityA == "83214" and cityB == "94016"
):
distance = {"distance": 980.0}
elif (cityA == "94016" and cityB == "94704") or (
cityA == "94704" and cityB == "94016"
):
distance = {"distance": 600.0}
elif (cityA == "94704" and cityB == "08540") or (
cityA == "08540" and cityB == "94704"
):
distance = {"distance": 2550.0}
elif (cityA == "94016" and cityB == "08540") or (
cityA == "08540" and cityB == "94016"
):
distance = {"distance": 1950.0}
elif (cityA == "62947" and cityB == "47329") or (
cityA == "47329" and cityB == "62947"
):
distance = {"distance": 1053.0}
elif (cityA == "94016" and cityB == "62947") or (
cityA == "62947" and cityB == "94016"
):
distance = {"distance": 780.0}
elif (cityA == "74532" and cityB == "94016") or (
cityA == "94016" and cityB == "74532"
):
distance = {"distance": 880.0}
else:
distance = {"error": "distance not found in database."}
if self.long_context:
distance["intermediaryCities"] = INTERMEDIARY_CITIES
return distance
def get_zipcode_based_on_city(self, city: str) -> Dict[str, str]:
"""
Gets the zipcode based on the city.
Args:
city (str): The name of the city.
Returns:
zipcode (str): The zipcode of the city.
"""
if city == "Rivermist":
return {"zipcode": "83214"}
elif city == "Stonebrook":
return {"zipcode": "74532"}
elif city == "Maplecrest":
return {"zipcode": "56108"}
elif city == "Silverpine":
return {"zipcode": "62947"}
elif city == "Shadowridge":
return {"zipcode": "71354"}
elif city == "Sunset Valley":
return {"zipcode": "83462"}
elif city == "Oakendale":
return {"zipcode": "47329"}
elif city == "Willowbend":
return {"zipcode": "52013"}
elif city == "Crescent Hollow":
return {"zipcode": "69238"}
elif city == "Autumnville":
return {"zipcode": "51479"}
elif city == "San Francisco":
return {"zipcode": "94016"}
else:
return {"zipcode": "00000"}
def set_navigation(self, destination: str) -> Dict[str, str]:
"""
Navigates to the destination.
Args:
destination (str): The destination to navigate in the format of street, city, state.
Returns:
status (str): The status of the navigation.
"""
self.destination = destination
return {"status": "Navigating to " + destination}
def check_tire_pressure(self):
"""
Checks the tire pressure of the vehicle.
Returns:
tirePressure (Dict): The tire pressure of the vehicle.
- frontLeftTirePressure (float): The pressure of the front left tire in psi.
- frontRightTirePressure (float): The pressure of the front right tire in psi.
- rearLeftTirePressure (float): The pressure of the rear left tire in psi.
- rearRightTirePressure (float): The pressure of the rear right tire in psi.
- healthy_tire_pressure (bool): True if the tire pressure is healthy, False otherwise.
- car_info (Dict): The metadata of the car.
"""
# This is the healthy standard the vehicle use, though the user might have different preferences
healthy_tire_pressure = (
30 <= (
self.frontLeftTirePressure
+ self.frontRightTirePressure
+ self.rearLeftTirePressure
+ self.rearRightTirePressure
) / 4 <= 35
)
tire_status = {
"frontLeftTirePressure": self.frontLeftTirePressure,
"frontRightTirePressure": self.frontRightTirePressure,
"rearLeftTirePressure": self.rearLeftTirePressure,
"rearRightTirePressure": self.rearRightTirePressure,
"healthy_tire_pressure": healthy_tire_pressure,
"car_info": {},
}
if self.long_context:
tire_status["car_info"] = CAR_STATUS_METADATA_EXTENSION
return tire_status
def find_nearest_tire_shop(self) -> Dict[str, str]:
"""
Finds the nearest tire shop.
Returns:
shopLocation (str): The location of the nearest tire shop.
"""
return {"shopLocation": "456 Oakwood Avenue, Rivermist, 83214"}
@@ -0,0 +1,299 @@
import os
import random
import time
from typing import Optional
from urllib.parse import urlparse
import html2text
import requests
from bs4 import BeautifulSoup
from serpapi import GoogleSearch
ERROR_TEMPLATES = [
"503 Server Error: Service Unavailable for url: {url}",
"429 Client Error: Too Many Requests for url: {url}",
"403 Client Error: Forbidden for url: {url}",
(
"HTTPSConnectionPool(host='{host}', port=443): Max retries exceeded with url: {path} "
"(Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x{id1:x}>, "
"'Connection to {host} timed out. (connect timeout=5)'))"
),
"HTTPSConnectionPool(host='{host}', port=443): Read timed out. (read timeout=5)",
(
"Max retries exceeded with url: {path} "
"(Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x{id2:x}>: "
"Failed to establish a new connection: [Errno -2] Name or service not known'))"
),
]
class WebSearchAPI:
def __init__(self):
self._api_description = "This tool belongs to the Web Search API category. It provides functions to search the web and browse search results."
self.show_snippet = True
# Note: The following two random generators are used to simulate random errors, but that feature is not currently used
# This one used to determine if we should simulate a random error
# Outcome (True means simulate error): [True, False, True, True, False, True, True, True, False, False, True, True, False, True, False, False, False, False, False, True]
self._random = random.Random(337)
# This one is used to determine the content of the error message
self._rng = random.Random(1053)
def _load_scenario(self, initial_config: dict, long_context: bool = False):
# We don't care about the long_context parameter here
# It's there to match the signature of functions in the multi-turn evaluation code
self.show_snippet = initial_config["show_snippet"]
def search_engine_query(
self,
keywords: str,
max_results: Optional[int] = 10,
region: Optional[str] = "wt-wt",
) -> list:
"""
This function queries the search engine for the provided keywords and region.
Args:
keywords (str): The keywords to search for.
max_results (int, optional): The maximum number of search results to return. Defaults to 10.
region (str, optional): The region to search in. Defaults to "wt-wt". Possible values include:
- xa-ar for Arabia
- xa-en for Arabia (en)
- ar-es for Argentina
- au-en for Australia
- at-de for Austria
- be-fr for Belgium (fr)
- be-nl for Belgium (nl)
- br-pt for Brazil
- bg-bg for Bulgaria
- ca-en for Canada
- ca-fr for Canada (fr)
- ct-ca for Catalan
- cl-es for Chile
- cn-zh for China
- co-es for Colombia
- hr-hr for Croatia
- cz-cs for Czech Republic
- dk-da for Denmark
- ee-et for Estonia
- fi-fi for Finland
- fr-fr for France
- de-de for Germany
- gr-el for Greece
- hk-tzh for Hong Kong
- hu-hu for Hungary
- in-en for India
- id-id for Indonesia
- id-en for Indonesia (en)
- ie-en for Ireland
- il-he for Israel
- it-it for Italy
- jp-jp for Japan
- kr-kr for Korea
- lv-lv for Latvia
- lt-lt for Lithuania
- xl-es for Latin America
- my-ms for Malaysia
- my-en for Malaysia (en)
- mx-es for Mexico
- nl-nl for Netherlands
- nz-en for New Zealand
- no-no for Norway
- pe-es for Peru
- ph-en for Philippines
- ph-tl for Philippines (tl)
- pl-pl for Poland
- pt-pt for Portugal
- ro-ro for Romania
- ru-ru for Russia
- sg-en for Singapore
- sk-sk for Slovak Republic
- sl-sl for Slovenia
- za-en for South Africa
- es-es for Spain
- se-sv for Sweden
- ch-de for Switzerland (de)
- ch-fr for Switzerland (fr)
- ch-it for Switzerland (it)
- tw-tzh for Taiwan
- th-th for Thailand
- tr-tr for Turkey
- ua-uk for Ukraine
- uk-en for United Kingdom
- us-en for United States
- ue-es for United States (es)
- ve-es for Venezuela
- vn-vi for Vietnam
- wt-wt for No region
Returns:
list: A list of search result dictionaries, each containing information such as:
- 'title' (str): The title of the search result.
- 'href' (str): The URL of the search result.
- 'body' (str): A brief description or snippet from the search result.
"""
backoff = 2 # initial back-off in seconds
params = {
"engine": "duckduckgo",
"q": keywords,
"kl": region,
"api_key": os.getenv("SERPAPI_API_KEY"),
}
# Infinite retry loop with exponential backoff
while True:
try:
search = GoogleSearch(params)
search_results = search.get_dict()
except Exception as e:
# If the underlying HTTP call raised a 429 we retry, otherwise propagate
if "429" in str(e):
wait_time = backoff + random.uniform(0, backoff)
error_block = (
"*" * 100
+ f"\n❗️❗️ [WebSearchAPI] Received 429 from SerpAPI. The number of requests sent using this API key exceeds the hourly throughput limit OR your account has run out of searches. Retrying in {wait_time:.1f} seconds…"
+ "*" * 100
)
print(error_block)
time.sleep(wait_time)
backoff = min(backoff * 2, 120) # cap the back-off
continue
else:
error_block = (
"*" * 100
+ f"\n❗️❗️ [WebSearchAPI] Error from SerpAPI: {str(e)}. This is not a rate-limit error, so it will not be retried."
+ "*" * 100
)
print(error_block)
return {"error": str(e)}
# SerpAPI sometimes returns the error in the payload instead of raising
if "error" in search_results and "429" in str(search_results["error"]):
wait_time = backoff + random.uniform(0, backoff)
error_block = (
"*" * 100
+ f"\n❗️❗️ [WebSearchAPI] Received 429 from SerpAPI. The number of requests sent using this API key exceeds the hourly throughput limit OR your account has run out of searches. Retrying in {wait_time:.1f} seconds…"
+ "*" * 100
)
print(error_block)
time.sleep(wait_time)
backoff = min(backoff * 2, 120)
continue
break # Success no rate-limit error detected
if "organic_results" not in search_results:
return {
"error": "Failed to retrieve the search results from server. Please try again later."
}
search_results = search_results["organic_results"]
# Convert the search results to the desired format
results = []
for result in search_results[:max_results]:
if self.show_snippet:
results.append(
{
"title": result["title"],
"href": result["link"],
"body": result["snippet"],
}
)
else:
results.append(
{
"title": result["title"],
"href": result["link"],
}
)
return results
def fetch_url_content(self, url: str, mode: str = "raw") -> str:
"""
This function retrieves content from the provided URL and processes it based on the selected mode.
Args:
url (str): The URL to fetch content from. Must start with 'http://' or 'https://'.
mode (str, optional): The mode to process the fetched content. Defaults to "raw".
Supported modes are:
- "raw": Returns the raw HTML content.
- "markdown": Converts raw HTML content to Markdown format for better readability, using html2text.
- "truncate": Extracts and cleans text by removing scripts, styles, and extraneous whitespace.
"""
if not url.startswith(("http://", "https://")):
raise ValueError(f"Invalid URL: {url}")
try:
# A header that mimics a browser request. This helps avoid 403 Forbidden errors.
# TODO: Is this the best way to do this?
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/112.0.0.0 Safari/537.36"
),
"Accept": (
"text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
),
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Referer": "https://www.google.com/",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document",
}
response = requests.get(url, headers=headers, timeout=20, allow_redirects=True)
response.raise_for_status()
# Note: Un-comment this when we want to simulate a random error
# Flip a coin to simulate a random error
# if self._random.random() < 0.95:
# return {"error": self._fake_requests_get_error_msg(url)}
# Process the response based on the mode
if mode == "raw":
return {"content": response.text}
elif mode == "markdown":
converter = html2text.HTML2Text()
markdown = converter.handle(response.text)
return {"content": markdown}
elif mode == "truncate":
soup = BeautifulSoup(response.text, "html.parser")
# Remove scripts and styles
for script_or_style in soup(["script", "style"]):
script_or_style.extract()
# Extract and clean text
text = soup.get_text(separator="\n", strip=True)
return {"content": text}
else:
raise ValueError(f"Unsupported mode: {mode}")
except Exception as e:
return {"error": f"An error occurred while fetching {url}: {str(e)}"}
def _fake_requests_get_error_msg(self, url: str) -> str:
"""
Return a realisticlooking requests/urllib3 error message.
"""
parsed = urlparse(url)
context = {
"url": url,
"host": parsed.hostname or "unknown",
"path": parsed.path or "/",
"id1": self._rng.randrange(0x10000000, 0xFFFFFFFF),
"id2": self._rng.randrange(0x10000000, 0xFFFFFFFF),
}
template = self._rng.choice(ERROR_TEMPLATES)
return template.format(**context)