diff --git a/examples/configs/grpo_adk_gemma.yaml b/examples/configs/grpo_adk_gemma.yaml new file mode 100644 index 0000000000..15b2faae08 --- /dev/null +++ b/examples/configs/grpo_adk_gemma.yaml @@ -0,0 +1,51 @@ +# GRPO configuration for unique numbers environment +defaults: "grpo_math_8B.yaml" + +grpo: + num_prompts_per_step: 32 + num_generations_per_prompt: 16 + max_rollout_turns: 20 + max_num_steps: 100 + val_at_start: false + +data: + add_system_prompt: false + shuffle: false + +checkpointing: + enabled: false + checkpoint_dir: "results/grpo-adk" + metric_name: "val_reward" + higher_is_better: true + keep_top_k: 3 + save_period: 10 + +env: + unique_numbers: + cfg: + max_turns: 15 + min_length: 5 + max_length: 10 + max_integer: 15 + +logger: + wandb_enabled: True + wandb: + project: "grpo-simulated-adk" + name: "gemma-4b-__NOW__" + + +policy: + train_global_batch_size: 512 + logprob_batch_size: 1 + model_name: google/gemma-3-4b-it + dynamic_batching: + enabled: True + sequence_packing: + enabled: False + tokenizer: + name: google/gemma-3-4b-it + chat_template: "{%- if add_bos_token|default(false) %}{{ bos_token }}{% endif %}{% for message in messages %}{% set role = 'model' if message['role'] == 'assistant' else message['role'] %}{{ '' + role + '\n' + message['content'] | trim + '\n' }}{% endfor %}{% if add_generation_prompt %}{{ 'model\n' }}{% endif %}" + +cluster: + gpus_per_node: 8 diff --git a/examples/configs/grpo_adk_llama8b.yaml b/examples/configs/grpo_adk_llama8b.yaml new file mode 100644 index 0000000000..3431383bff --- /dev/null +++ b/examples/configs/grpo_adk_llama8b.yaml @@ -0,0 +1,45 @@ +# GRPO configuration for unique numbers environment +defaults: "grpo_math_8B.yaml" + +grpo: + num_prompts_per_step: 32 + num_generations_per_prompt: 16 + max_rollout_turns: 20 + max_num_steps: 100 + val_at_start: false + +data: + add_system_prompt: false + shuffle: false + +checkpointing: + enabled: false + checkpoint_dir: "results/grpo-adk" + metric_name: "val_reward" + higher_is_better: true + keep_top_k: 3 + save_period: 10 + +env: + unique_numbers: + cfg: + max_turns: 15 + min_length: 5 + max_length: 10 + max_integer: 15 + +logger: + wandb_enabled: True + wandb: + project: "grpo-simulated-adk" + name: "llama-8b-__NOW__" + +policy: + train_global_batch_size: 512 + dynamic_batching: + enabled: False + tokenizer: + chat_template: '{%- if add_bos_token|default(false) %}{{ bos_token }}{% endif %}{% for message in messages %}{{ "<|start_header_id|>" + message.role + "<|end_header_id|>\n\n" + message.content | trim + "<|eot_id|>" }}{% endfor %}{% if add_generation_prompt %}{{ "<|start_header_id|>assistant<|end_header_id|>\n\n" }}{% endif %}' + +cluster: + gpus_per_node: 8 diff --git a/examples/run_grpo_unique_numbers_w_adk.py b/examples/run_grpo_unique_numbers_w_adk.py new file mode 100644 index 0000000000..e89731115e --- /dev/null +++ b/examples/run_grpo_unique_numbers_w_adk.py @@ -0,0 +1,259 @@ +"""Run GRPO with the Unique Numbers Simulator using ADK. + +This script sets up and executes the Group Relative Policy Optimization (GRPO) algorithm +in a multi-turn conversational environment powered by the ADK framework. + +### Task Overview +The objective is to train an agent to guess the number of unique integers in a list generated by a simulated user. +The interaction is structured as a turn-based dialogue: +- The user generates a list of integers. +- The agent queries specific positions in the list (by index). +- The user replies with the value at that index (if available). +- The agent continues the interaction until it makes a final guess at the number of unique integers. + +### Environment Details +The environment is a simulated user that: +- Randomly generates a list of integers at setup. +- Responds to the agent's queries using an LLM via the ADK endpoint. +- Optionally evaluates the agent's final guess using an LLM-based grader (included for extensibility, though not essential for this task). + +### Example Usage + uv run python examples/run_grpo_unique_numbers_w_adk.py + +### Requirements +- A working ADK environment with access to a compatible LLM endpoint. + For the default Gemini endpoint, the following environment variables must be set: + - `GOOGLE_GENAI_USE_VERTEXAI=1` + - `GOOGLE_CLOUD_PROJECT="your-project-id"` + - `GOOGLE_CLOUD_LOCATION="your-location"` + +- A properly configured GRPO YAML file. + By default, the script uses: + `examples/configs/grpo_adk_llama8b.yaml` +""" + +import argparse +import itertools +import os +import pprint +import random +from datetime import datetime, timedelta +from typing import Iterator + +from omegaconf import OmegaConf +from torch.utils.data import IterableDataset +from transformers import AutoTokenizer + +from nemo_rl.algorithms.grpo import MasterConfig, grpo_train, setup +from nemo_rl.algorithms.utils import get_tokenizer +from nemo_rl.data.interfaces import DatumSpec, LLMMessageLogType +from nemo_rl.distributed.ray_actor_environment_registry import ( + get_actor_python_env, +) +from nemo_rl.distributed.virtual_cluster import init_ray +from nemo_rl.environments.simulated_user.prompt import starting_user_prompt +from nemo_rl.environments.simulated_user.unique_numbers import ( + UniqueNumbersEnv, + UniqueNumbersMetadata, +) +from nemo_rl.models.generation import configure_generation_config +from nemo_rl.utils.config import load_config, parse_hydra_overrides +from nemo_rl.utils.logger import get_next_experiment_dir + +OmegaConf.register_new_resolver("mul", lambda a, b: a * b) + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Run GRPO with unique numbers simulator" + ) + parser.add_argument( + "--config", type=str, default=None, help="Path to YAML config file" + ) + args, overrides = parser.parse_known_args() + return args, overrides + + +def generate_datum( + tokenizer: AutoTokenizer, + env_cfg: dict, + task_name: str, + idx: int, + add_system_prompt: bool, +) -> DatumSpec: + # please check the specific chat_template in the yaml file + formatted_prompt = tokenizer.apply_chat_template( + [{"role": "user", "content": starting_user_prompt}], + tokenize=False, + # add_system_prompt=add_system_prompt, + add_bos_token=True, + add_generation_prompt=True, + add_special_tokens=False, + ) + token_ids = tokenizer( + formatted_prompt, return_tensors="pt", add_special_tokens=False + )["input_ids"][0] + + def _generate_numbers( + min_length, max_length, max_integer, default_max_turns + ) -> UniqueNumbersMetadata: + length = random.randint(min_length, max_length) + numbers = [random.randint(0, max_integer) for _ in range(length)] + return UniqueNumbersMetadata( + numbers=numbers, + unique_count=len(set(numbers)), + turn=0, + max_turns=default_max_turns, + ) + + metadata = _generate_numbers( + min_length=env_cfg["cfg"]["min_length"], + max_length=env_cfg["cfg"]["max_length"], + max_integer=env_cfg["cfg"]["max_integer"], + default_max_turns=env_cfg["cfg"]["max_turns"], + ) + + message_log: LLMMessageLogType = [ + {"role": "user", "content": formatted_prompt, "token_ids": token_ids} + ] + return { + "message_log": message_log, + "length": len(token_ids), + "extra_env_info": metadata, + "loss_multiplier": 1.0, + "idx": idx, + "task_name": task_name, + } + + +class IterableNumbersDataset(IterableDataset): + def __init__(self, tokenizer, env_cfg, task_name, add_system_prompt, length): + super().__init__() + self.tokenizer = tokenizer + self.env_cfg = env_cfg + self.task_name = task_name + self.add_system_prompt = add_system_prompt + self.length = length + + def __iter__(self) -> Iterator[DatumSpec]: + for i in itertools.count(): + yield generate_datum( + tokenizer=self.tokenizer, + env_cfg=self.env_cfg, + task_name=self.task_name, + idx=i, + add_system_prompt=self.add_system_prompt, + ) + + def __len__(self): + return self.length + + +def setup_data(tokenizer, env_cfg, task_name, length, val_length, add_system_prompt): + env_config = env_cfg[task_name] + env = UniqueNumbersEnv.options( # type: ignore # it's wrapped with ray.remote + num_gpus=0, + runtime_env={ + "py_executable": get_actor_python_env( + "nemo_rl.environments.simulated_user.unique_numbers.UniqueNumbersEnv" + ), + "env_vars": dict(os.environ), # Pass thru all user environment variables + }, + ).remote(cfg=dict(env_config["cfg"])) + + task_to_env = {task_name: env} + + train_ds = IterableNumbersDataset( + tokenizer=tokenizer, + env_cfg=env_config, + task_name=task_name, + add_system_prompt=add_system_prompt, + length=length, + ) + val_ds = IterableNumbersDataset( + tokenizer=tokenizer, + env_cfg=env_config, + task_name=task_name, + add_system_prompt=add_system_prompt, + length=val_length, + ) + val_task_to_env = task_to_env + return train_ds, val_ds, task_to_env, val_task_to_env + + +def main(): + args, overrides = parse_args() + if not args.config: + args.config = os.path.join( + os.path.dirname(__file__), "configs", "grpo_adk_llama8b.yaml" + ) + config = load_config(args.config) + if overrides: + config = parse_hydra_overrides(config, overrides) + config: MasterConfig = OmegaConf.to_container(config, resolve=True) + + now_pst = datetime.utcnow() + timedelta(hours=-7) + config["logger"]["wandb"]["name"] = config["logger"]["wandb"]["name"].replace( + "__NOW__", now_pst.strftime("%m/%d-%H:%M") + ) + + config["logger"]["log_dir"] = get_next_experiment_dir(config["logger"]["log_dir"]) + if config["checkpointing"]["enabled"]: + print( + f"\U0001f4ca Using checkpoint directory: {config['checkpointing']['checkpoint_dir']}" + ) + + pprint.pprint(config) + + init_ray() + + tokenizer = get_tokenizer(config["policy"]["tokenizer"]) + config["policy"]["generation"] = configure_generation_config( + config["policy"]["generation"], tokenizer + ) + + ds_length = ( + config["grpo"]["num_prompts_per_step"] + * config["grpo"]["num_generations_per_prompt"] + * config["grpo"]["max_num_steps"] + ) + dataset, val_dataset, task_to_env, val_task_to_env = setup_data( + tokenizer=tokenizer, + env_cfg=config["env"], + task_name="unique_numbers", + length=ds_length, + val_length=config["grpo"]["max_val_samples"], + add_system_prompt=config["data"]["add_system_prompt"], + ) + + ( + policy, + policy_generation, + cluster, + dataloader, + val_dataloader, + loss_fn, + logger, + checkpointer, + grpo_state, + master_config, + ) = setup(config, tokenizer, dataset, val_dataset) + + grpo_train( + policy, + policy_generation, + dataloader, + val_dataloader, + tokenizer, + loss_fn, + task_to_env, + val_task_to_env, + logger, + checkpointer, + grpo_state, + master_config, + ) + + +if __name__ == "__main__": + main() diff --git a/nemo_rl/distributed/ray_actor_environment_registry.py b/nemo_rl/distributed/ray_actor_environment_registry.py index e300aec54b..42ac4022dd 100644 --- a/nemo_rl/distributed/ray_actor_environment_registry.py +++ b/nemo_rl/distributed/ray_actor_environment_registry.py @@ -23,6 +23,7 @@ "nemo_rl.environments.math_environment.MathEnvironment": PY_EXECUTABLES.SYSTEM, "nemo_rl.environments.code_environment.CodeEnvironment": PY_EXECUTABLES.SYSTEM, "nemo_rl.environments.games.sliding_puzzle.SlidingPuzzleEnv": PY_EXECUTABLES.SYSTEM, + "nemo_rl.environments.simulated_user.unique_numbers.UniqueNumbersEnv": PY_EXECUTABLES.ADK, "nemo_rl.environments.tools.retriever.RAGEnvironment": PY_EXECUTABLES.SYSTEM, } diff --git a/nemo_rl/distributed/virtual_cluster.py b/nemo_rl/distributed/virtual_cluster.py index 6e0a75b880..0ef5d8f698 100644 --- a/nemo_rl/distributed/virtual_cluster.py +++ b/nemo_rl/distributed/virtual_cluster.py @@ -54,6 +54,8 @@ class PY_EXECUTABLES: # aren't installed. Simple workaround is to always run the mcore py_executable with --reinstall. MCORE = "uv run --reinstall --extra mcore" + ADK = "uv run --locked --extra adk" + @ray.remote # pragma: no cover def _get_node_ip_and_free_port() -> tuple[str, int]: diff --git a/nemo_rl/environments/interfaces.py b/nemo_rl/environments/interfaces.py index b869c32df7..47c7405051 100644 --- a/nemo_rl/environments/interfaces.py +++ b/nemo_rl/environments/interfaces.py @@ -46,7 +46,7 @@ class EnvironmentReturn(NamedTuple, Generic[MetadataT]): next_stop_strings: list[list[str] | None] | list[None] rewards: Tensor terminateds: Tensor - answers: list[str | None] | None + answers: list[str | None] | None = None class EnvironmentInterface(abc.ABC, Generic[MetadataT]): diff --git a/nemo_rl/environments/simulated_user/__init__.py b/nemo_rl/environments/simulated_user/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nemo_rl/environments/simulated_user/adk_utils.py b/nemo_rl/environments/simulated_user/adk_utils.py new file mode 100644 index 0000000000..97cd1c9b3e --- /dev/null +++ b/nemo_rl/environments/simulated_user/adk_utils.py @@ -0,0 +1,192 @@ +import asyncio +import logging +import random + +# Initialize logging +logging.basicConfig( + format="[%(asctime)s] [%(levelname)s] %(message)s", + level=logging.WARNING, +) +logger = logging.getLogger(__name__) + + +# Define the agents +def create_agent( + instruction: str | None = None, + name: str = "simulated_user", + model: str = "gemini-2.0-flash", +): + from google.adk.agents import Agent + from google.genai import types + + return Agent( + model=model, + name=name, + description="Agent", + instruction=instruction + or "You are a helpful assistant that help people answer questions.", + generate_content_config=types.GenerateContentConfig( + safety_settings=[ + types.SafetySetting( + category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + threshold=types.HarmBlockThreshold.OFF, + ), + ] + ), + ) + + +def get_session_from_runner(runner, user_id: str): + app_session_map = runner.session_service.sessions + assert len(app_session_map) == 1, "Expected exactly one app in session_service" + user_sessions_map = next(iter(app_session_map.values())) + sessions = user_sessions_map[user_id] + assert len(sessions) == 1, "Expected exactly one user in app session" + return next(iter(sessions.values())) + + +def get_agent_instruction_from_runner(runner): + return runner.agent.instruction + + +def extract_conversation_history(runner, user_id: str, silence: bool = True): + session = get_session_from_runner(runner, user_id) + instruction = get_agent_instruction_from_runner(runner) + convo = [{"role": "instruction", "content": instruction}] + for event in session.events: + if event.content.parts and event.content.parts[0].text: + convo.append({"role": event.author, "content": event.content.parts[0].text}) + if not silence: + logger.info(f"[{convo[-1]['role']}]: {convo[-1]['content']}") + return session.id, convo + + +async def run_prompt_async( + runner, + user_id: str, + new_message: str, + silence: bool = True, + max_retries: int = 3, + initial_delay: float = 2, +) -> str: + from google.genai import types + from google.genai.errors import ServerError + + new_message = new_message.strip() + content = types.Content(role="user", parts=[types.Part.from_text(text=new_message)]) + if not silence: + logger.info(f"** [User]->|||{new_message}|||") + + session = get_session_from_runner(runner, user_id) + + retries = 0 + delay = initial_delay + while retries < max_retries: + try: + async for event in runner.run_async( + user_id=session.user_id, + session_id=session.id, + new_message=content, + ): + if event.content.parts and event.content.parts[0].text: + if not silence: + logger.info( + f"** [{event.author}]->|||{event.content.parts[0].text.strip()}|||" + ) + return event.content.parts[0].text.strip() + else: + return "" + except ServerError as e: + retries += 1 + delay_with_jitter = delay + (random.random() * 2 - 1) * (delay * 0.5) + logger.error( + f"Gemini API call (with message {new_message}) failed with ServerError {e} (attempt {retries}/{max_retries}). Retrying in {delay_with_jitter} seconds..." + ) + await asyncio.sleep(delay_with_jitter) + delay *= 2 # Exponential backoff + except Exception as e: + logger.error( + f"Gemini API call (with message {new_message}) failed with an unexpected error: {e}." + ) + return f"" + + logger.error( + f"Gemini API call (with message {new_message}) reached maximum retries ({max_retries}) without success." + ) + return f"" + + +async def setup_runner_async(agent, app_name: str, user_id: str): + from google.adk.runners import Runner + from google.adk.sessions import InMemorySessionService + + runner = Runner( + agent=agent, app_name=app_name, session_service=InMemorySessionService() + ) + await runner.session_service.create_session(app_name=app_name, user_id=user_id) + return runner + + +async def main(): + from google.adk.runners import Runner + from google.adk.sessions import InMemorySessionService + + sample_id_1 = "sample_1" + sample_id_2 = "sample_2" + + # Set up simulated user runner + simulated_user_app_name = "su_app" + simulated_user_runner = Runner( + agent=create_agent(name="simulated_user"), + app_name=simulated_user_app_name, + session_service=InMemorySessionService(), + ) + + await simulated_user_runner.session_service.create_session( + app_name=simulated_user_app_name, user_id=sample_id_1 + ) + await simulated_user_runner.session_service.create_session( + app_name=simulated_user_app_name, user_id=sample_id_2 + ) + + # setup grader runner + grader_app_name = "grader_app" + grader_instruction = "You are a helpful agent that can grade the correctness and coherent of a conversation. Please only give an integer as the score." + grader_runner = await setup_runner_async( + agent=create_agent(name="grader", instruction=grader_instruction), + app_name=grader_app_name, + user_id=sample_id_1, + ) + + # Simulated user interactions + await run_prompt_async( + simulated_user_runner, sample_id_1, "what is 2*3+5?", silence=False + ) + await run_prompt_async(simulated_user_runner, sample_id_2, "what is 2*3-5?") + await run_prompt_async(simulated_user_runner, sample_id_1, "Now add another 10.") + await run_prompt_async(simulated_user_runner, sample_id_2, "Now add another 100.") + + # Print conversation + logger.info("-" * 100) + _, convo1 = extract_conversation_history( + simulated_user_runner, sample_id_1, silence=False + ) + logger.info("-" * 100) + _, convo2 = extract_conversation_history( + simulated_user_runner, sample_id_2, silence=False + ) + logger.info("-" * 100) + + # Grade conversation + await run_prompt_async( + grader_runner, + sample_id_1, + f"Grade the above conversation and give a score between 0-10. \n\n{convo1}", + silence=False, + ) + logger.info("-" * 100) + logger.info("DONE!") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/nemo_rl/environments/simulated_user/prompt.py b/nemo_rl/environments/simulated_user/prompt.py new file mode 100644 index 0000000000..575c10fb7b --- /dev/null +++ b/nemo_rl/environments/simulated_user/prompt.py @@ -0,0 +1,30 @@ +starting_user_prompt = ( + "I will play a game with you. I have a list of integers in mind and can NOT tell you. " + "Your goal is to guess the count of UNIQUE numbers in my list. The only 2 things you can do is the following: " + "You can either ask me 'what is number k?' to get the number at position k in my list, " + "or answer 'there are m unique numbers' whenever you feel you want to make a guess. " + "Please do not say anything else. You cannot ask me to provide the list of integers." +) + + +simulated_user_instruction = """ +You are a simulated user in a game where the assistant must figure out how many unique numbers you have. +You have a list of numbers (which may contain duplicates) that you will not reveal to the assistant. +The assistant can ask you questions of the form "What is number k?" where k is a 1-based index into your list of numbers. +You should respond with the number at that index. +The assistant can also make a guess by saying "There are m unique numbers" where m is their guess for the count of unique numbers. +If the assistant makes a correct guess, you will reward it. If the guess is incorrect, you will penalize it. + +Here is your list of numbers: {numbers}. +""".strip() + +grader_instruction = """ +Your are a strict grader to evaluate whether the assistant has properly guessed the count of unique numbers. +Here is your list of numbers: {numbers}. +You will see a conversation between the assistant and a simulated user who has this list of numbers. +You will need to evaluete in the end whether the assistant has made a correct guess of the count of unique numbers. +If the assistant made a correct guess, give it a score of 1. If the guess is incorrect, give it a score of 0. +If assistant made a correct guess but you feel the assistant has asked too many questions, please give a score between 0 and 1. +If the assistant never made a guess, give it a score of 0. +Please only output an integer score between 0 and 1, and nothing else. +""".strip() diff --git a/nemo_rl/environments/simulated_user/unique_numbers.py b/nemo_rl/environments/simulated_user/unique_numbers.py new file mode 100644 index 0000000000..f0ee4fda38 --- /dev/null +++ b/nemo_rl/environments/simulated_user/unique_numbers.py @@ -0,0 +1,296 @@ +"""Simulated user environment for counting unique numbers.""" + +from __future__ import annotations + +import asyncio +import os +import re +from concurrent.futures import ThreadPoolExecutor +from typing import Any, Optional, TypedDict + +import ray +import torch + +from nemo_rl.data.interfaces import LLMMessageLogType +from nemo_rl.distributed.batched_data_dict import BatchedDataDict +from nemo_rl.environments.interfaces import EnvironmentInterface, EnvironmentReturn +from nemo_rl.environments.simulated_user.prompt import ( + grader_instruction, + simulated_user_instruction, +) + +PENALTY_FOR_NO_GUESS = -0.2 +PENALTY_FOR_INCORRECT_GUESS = 0.0 +PENALTY_FOR_EVERY_ASK = 0.0 +PENALTY_FOR_INCORRECT_FORMAT = 0.0 + +ADK_LOG_FOLDER = None # 'logs/adk' or None to disable logging +if ADK_LOG_FOLDER is not None: + os.makedirs(ADK_LOG_FOLDER, exist_ok=True) + +GEMINI_CALL_MAX_WORKERS = ( + 64 # if 1 then it will be single-threaded, otherwise it will use ThreadPoolExecutor +) + + +class UniqueNumbersConfig(TypedDict, total=False): + """Configuration for :class:`UniqueNumbersEnv`.""" + + min_length: int + max_length: int + max_turns: int + + +class UniqueNumbersMetadata(TypedDict): + """Metadata for a UniqueNumbersEnv episode.""" + + numbers: list[int] + unique_count: int + turn: int + max_turns: int + simulated_user_runner: Optional[Any] + grader_runner: Optional[Any] + + +class _UniqueNumbersRunner: + query_re = re.compile(r"what is number (\d+)\??$", re.IGNORECASE) + guess_re = re.compile(r"there are (\d+) unique number", re.IGNORECASE) + + def _maybe_dump_adk_messages_to_file( + self, + runner: Any, + user_id: str, + log_name_suffix: str = "", + dump_folder: str = ADK_LOG_FOLDER, + ): + from nemo_rl.environments.simulated_user.adk_utils import ( + extract_conversation_history, + ) + + if dump_folder is None: + return + session_id, messages = extract_conversation_history( + runner, user_id, silence=True + ) + file_name = f"{user_id}_{session_id}{log_name_suffix}.log" + with open(os.path.join(dump_folder, file_name), "a") as f: + for message in messages: + f.write(f"[{message['role']}]:|||{message['content']}|||\n") + + def process_turn( + self, message_log: LLMMessageLogType, metadata: UniqueNumbersMetadata + ) -> tuple[dict[str, str], float, bool, None, Optional[UniqueNumbersMetadata]]: + from nemo_rl.environments.simulated_user.adk_utils import ( + create_agent, + run_prompt_async, + setup_runner_async, + ) + + turn = metadata["turn"] + max_turns = metadata["max_turns"] + + if ( + "simulated_user_runner" not in metadata + or metadata["simulated_user_runner"] is None + ): + instruction = simulated_user_instruction.replace( + "{numbers}", str(metadata["numbers"]) + ) + simulated_user_agent = create_agent( + name="simulated_user", model="gemini-2.0-flash", instruction=instruction + ) + metadata["simulated_user_runner"] = asyncio.run( + setup_runner_async( + simulated_user_agent, "simulated_user_app", "simulated_user" + ) + ) + + if "grader_runner" not in metadata or metadata["grader_runner"] is None: + instruction = grader_instruction.replace( + "{numbers}", str(metadata["numbers"]) + ) + grader_agent = create_agent( + name="grader", model="gemini-2.0-flash", instruction=instruction + ) + metadata["grader_runner"] = asyncio.run( + setup_runner_async(grader_agent, "grader_app", "grader") + ) + + if turn >= max_turns: + self._maybe_dump_adk_messages_to_file( + metadata["simulated_user_runner"], "simulated_user", "_maxturns" + ) + return ( + {"role": "user", "content": ""}, + PENALTY_FOR_NO_GUESS, + True, + None, + None, + ) + + last_msg = "" + if message_log and message_log[-1]["role"] == "assistant": + last_msg = message_log[-1]["content"].strip() + + if not last_msg: + # no last message from assistant, assuming done + return ( + {"role": "user", "content": ""}, + PENALTY_FOR_NO_GUESS, + True, + None, + None, + ) + + # simulate user utterance via ADK + query_match = self.query_re.search(last_msg) + if query_match: + simulated_content = asyncio.run( + run_prompt_async( + metadata["simulated_user_runner"], + "simulated_user", + last_msg, + silence=True, + ) + ) + next_meta = { + "numbers": metadata["numbers"], + "unique_count": metadata["unique_count"], + "turn": turn + 1, + "max_turns": max_turns, + "simulated_user_runner": metadata.get("simulated_user_runner", None), + "grader_runner": metadata.get("grader_runner", None), + } + return ( + {"role": "user", "content": simulated_content}, + PENALTY_FOR_EVERY_ASK, + False, + None, + next_meta, + ) + + # calculate reward if the assistant made a guess + guess_match = self.guess_re.search(last_msg) + if guess_match: + m = int(guess_match.group(1)) + reward = ( + 1.0 if m == metadata["unique_count"] else PENALTY_FOR_INCORRECT_GUESS + ) + + # grade the conversation via ADK grader + if metadata["grader_runner"] is not None: + convo_str = "\n".join( + [f"{msg['role']}: {msg['content']}" for msg in message_log] + ) + grading_prompt = f"Here is the converstation \n{convo_str}\nAnd please give the score between 0 and 1." + grading_response = asyncio.run( + run_prompt_async( + metadata["grader_runner"], + "grader", + grading_prompt, + silence=True, + ) + ) + try: + grade = int(re.search(r"(\d+)", grading_response).group(1)) + reward = (reward + grade) / 2.0 + except Exception as e: + print( + f"Failed to parse grade from grader response '{grading_response}': {e}" + ) + + self._maybe_dump_adk_messages_to_file( + metadata["simulated_user_runner"], "simulated_user", "_stop" + ) + self._maybe_dump_adk_messages_to_file(metadata["grader_runner"], "grader") + + return {"role": "user", "content": ""}, reward, True, None, None + + # default response + next_meta = { + "numbers": metadata["numbers"], + "unique_count": metadata["unique_count"], + "turn": turn + 1, + "max_turns": max_turns, + "simulated_user_runner": metadata.get("simulated_user_runner", None), + "grader_runner": metadata.get("grader_runner", None), + } + help_msg = "Please ask 'what is number k?' or say 'there are m unique numbers'." + return ( + {"role": "user", "content": help_msg}, + PENALTY_FOR_INCORRECT_FORMAT, + False, + None, + next_meta, + ) + + +@ray.remote +class UniqueNumbersEnv(EnvironmentInterface): + """Environment where the LLM must deduce the count of unique numbers.""" + + def __init__(self, cfg: Optional[UniqueNumbersConfig] = None): + cfg = cfg or {} + self.min_length = cfg.get("min_length", 3) + self.max_length = cfg.get("max_length", 7) + self.default_max_turns = cfg.get("max_turns", 10) + + self.runner = _UniqueNumbersRunner() + + def step( + self, + message_log_batch: list[LLMMessageLogType], + metadata_batch: list[Optional[UniqueNumbersMetadata]], + ) -> EnvironmentReturn: + args = [] + for log, meta in zip(message_log_batch, metadata_batch): + assert meta is not None, "Metadata must not be None for UniqueNumbersEnv." + assert meta["numbers"] is not None, "Numbers must not be None in metadata." + assert meta["unique_count"] > 0, ( + "Unique count must be greater than 0 in metadata." + ) + args.append((log, meta)) + + # Process either serially or in parallel + if GEMINI_CALL_MAX_WORKERS is None or GEMINI_CALL_MAX_WORKERS <= 1: + results = [self.runner.process_turn(log, meta) for log, meta in args] + else: + with ThreadPoolExecutor(max_workers=GEMINI_CALL_MAX_WORKERS) as executor: + results = list( + executor.map(lambda p: self.runner.process_turn(*p), args) + ) + + observations, rewards, terminateds, stop_strings, next_metadata = ( + [], + [], + [], + [], + [], + ) + for obs, rew, term, stops, meta in results: + observations.append(obs) + rewards.append(rew) + terminateds.append(term) + stop_strings.append(stops) + next_metadata.append(meta) + + return EnvironmentReturn( + observations=observations, + metadata=next_metadata, + next_stop_strings=stop_strings, + rewards=torch.tensor(rewards, dtype=torch.float32), + terminateds=torch.tensor(terminateds, dtype=torch.bool), + answers = None, + ) + + def shutdown(self) -> None: # pragma: no cover + pass + + def global_post_process_and_metrics( + self, batch: BatchedDataDict + ) -> tuple[BatchedDataDict, dict]: + final_rewards = batch.get( + "total_reward", torch.tensor([0.0] * len(batch["idx"])) + ) + avg_reward = final_rewards.mean().item() if len(final_rewards) > 0 else 0.0 + return batch, {"unique_numbers_avg_reward": avg_reward} diff --git a/nemo_rl/experience/rollouts.py b/nemo_rl/experience/rollouts.py index ed8fa5b890..e5770bcc44 100644 --- a/nemo_rl/experience/rollouts.py +++ b/nemo_rl/experience/rollouts.py @@ -363,6 +363,11 @@ def run_multi_turn_rollout( if len(active_indices) == 0: break + if max_rollout_turns > 1: + print( + f"▶ ▶ ▶ Running rollout turn {turn + 1} / {max_rollout_turns} with {len(active_indices)} active samples..." + ) + active_samples_per_turn.append(len(active_indices)) # Convert LLMMessageLogType to FlatMessagesType for generation @@ -387,7 +392,6 @@ def run_multi_turn_rollout( "stop_strings": active_stop_strings, } ) - # generate_responses updates active_batch["message_log"] in-place active_batch, generated_ids, gen_metrics = generate_responses( policy_generation, @@ -416,11 +420,26 @@ def run_multi_turn_rollout( truncation_mask = torch.zeros_like(env_output.terminateds, dtype=torch.bool) for i, global_idx in enumerate(active_indices.tolist()): env_obs_content = env_output.observations[i]["content"] - # Tokenize the raw content from the environment - # TODO @sahilj: handle if we want these subsequent messages to have a chat template - tokenized_obs = tokenizer( - env_obs_content, return_tensors="pt", add_special_tokens=False - ).input_ids[0] + # Tokenize the raw content from the environment into chat format if needed + env_role = env_output.observations[i]["role"].lower() + if env_role in {"user", "assistant", "system"}: + formatted_obs = tokenizer.apply_chat_template( + [{"role": env_role, "content": env_obs_content.strip()}], + tokenize=False, + add_generation_prompt=True, + ) + tokenized_obs = tokenizer( + formatted_obs, return_tensors="pt", add_special_tokens=False + ).input_ids[0] + # remove the bos token if added after `apply_chat_template` + if len(formatted_obs) > 0 and hasattr(tokenizer, "bos_token_id") and formatted_obs[0] == tokenizer.bos_token_id: + formatted_obs = formatted_obs[1:] + else: + formatted_obs = env_obs_content.strip() + tokenized_obs = tokenizer( + formatted_obs, return_tensors="pt", add_special_tokens=False + ).input_ids[0] + # tokenizer returns torch.float32 when env_obs_content is empty tokenized_obs = tokenized_obs.to(dtype=torch.int64) @@ -443,7 +462,7 @@ def run_multi_turn_rollout( tokenized_env_obs_message = { "role": env_output.observations[i]["role"], - "content": env_obs_content, + "content": formatted_obs, "token_ids": tokenized_obs, } current_batch["message_log"][global_idx].append(tokenized_env_obs_message) @@ -673,9 +692,24 @@ async def run_sample_multi_turn_rollout( terminated = env_output.terminateds[0].item() env_obs_content = env_output.observations[0]["content"] # Tokenize environment response - tokenized_obs = tokenizer( - env_obs_content, return_tensors="pt", add_special_tokens=False - ).input_ids[0] + env_role = env_output.observations[0]["role"].lower() + if env_role in {"user", "assistant", "system"}: + formatted_obs = tokenizer.apply_chat_template( + [{"role": env_role, "content": env_obs_content.strip()}], + tokenize=False, + add_generation_prompt=True, + ) + tokenized_obs = tokenizer( + formatted_obs, return_tensors="pt", add_special_tokens=False + ).input_ids[0] + # remove the bos token if added after `apply_chat_template` + if len(formatted_obs) > 0 and hasattr(tokenizer, "bos_token_id") and formatted_obs[0] == tokenizer.bos_token_id: + formatted_obs = formatted_obs[1:] + else: + formatted_obs = env_obs_content.strip() + tokenized_obs = tokenizer( + formatted_obs, return_tensors="pt", add_special_tokens=False + ).input_ids[0] # Check for sequence length overflow if input_lengths + gen_token_count + len(tokenized_obs) >= max_seq_len: @@ -689,7 +723,7 @@ async def run_sample_multi_turn_rollout( env_message = { "role": env_output.observations[0]["role"], - "content": env_obs_content, + "content": formatted_obs, "token_ids": tokenized_obs, } current_message_log.append(env_message) diff --git a/pyproject.toml b/pyproject.toml index cb7b6f5227..a803b148ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,9 @@ mcore = [ # https://github.com/facebookresearch/xformers/blob/8354497deb2c04c67fbb2e2ad911e86530da0e90/xformers/ops/fmha/flash.py#L76 "flash-attn==2.7.4.post1", ] +adk = [ + "google-adk", +] [dependency-groups] diff --git a/tests/unit/environments/test_simulated_user.py b/tests/unit/environments/test_simulated_user.py new file mode 100644 index 0000000000..69794f9d22 --- /dev/null +++ b/tests/unit/environments/test_simulated_user.py @@ -0,0 +1,261 @@ +import asyncio +import re +import sys +import types + +import pytest + +# Stub out external modules that are not available in the test environment +google_module = sys.modules.get("google", types.ModuleType("google")) +google_module.adk = types.ModuleType("google.adk") +google_module.adk.agents = types.ModuleType("google.adk.agents") +google_module.adk.agents.Agent = object +google_module.adk.Agent = object +google_module.adk.runners = types.ModuleType("google.adk.runners") +google_module.adk.runners.Runner = object +google_module.adk.sessions = types.ModuleType("google.adk.sessions") +google_module.adk.sessions.InMemorySessionService = object +google_module.genai = types.ModuleType("google.genai") + + +class _Part: + def __init__(self, text: str): + self.text = text + + @classmethod + def from_text(cls, text: str): + return cls(text) + + +class _Content: + def __init__(self, role: str | None = None, parts: list[_Part] | None = None): + self.role = role + self.parts = parts or [] + + +google_module.genai.types = types.SimpleNamespace( + GenerateContentConfig=object, + SafetySetting=object, + HarmCategory=object, + HarmBlockThreshold=object, + Content=_Content, + Part=_Part, +) +google_module.genai.errors = types.SimpleNamespace(ServerError=Exception) +sys.modules.setdefault("google.adk", google_module.adk) +sys.modules.setdefault("google.adk.agents", google_module.adk.agents) +sys.modules.setdefault("google.adk.runners", google_module.adk.runners) +sys.modules.setdefault("google.adk.sessions", google_module.adk.sessions) +sys.modules.setdefault("google.genai", google_module.genai) +sys.modules.setdefault("google.genai.types", google_module.genai.types) +sys.modules.setdefault("google.genai.errors", google_module.genai.errors) + +ray_module = types.ModuleType("ray") +ray_module.remote = lambda cls=None, **_: cls +sys.modules.setdefault("ray", ray_module) + +torch_module = types.ModuleType("torch") +torch_module.tensor = lambda data, **_: data +torch_module.float32 = "float32" +torch_module.bool = "bool" +torch_module.Tensor = object +torch_module.distributed = types.SimpleNamespace(ProcessGroup=object) +torch_module.device = object +sys.modules.setdefault("torch", torch_module) + +transformers_module = types.ModuleType("transformers") +transformers_module.PreTrainedTokenizerBase = object +sys.modules.setdefault("transformers", transformers_module) + +from nemo_rl.environments.simulated_user import adk_utils, unique_numbers + + +# Dummy runner object used for mocking run_prompt_async behaviour +class DummyRunner: + def __init__(self, numbers): + self.numbers = numbers + + +def _make_metadata(numbers): + return { + "numbers": numbers, + "unique_count": len(set(numbers)), + "turn": 0, + "max_turns": 5, + "simulated_user_runner": DummyRunner(numbers), + "grader_runner": DummyRunner(numbers), + } + + +@pytest.fixture +def patch_unique_numbers(monkeypatch): + async def fake_run_prompt_async(runner, user_id, msg, silence=True): + match = re.search(r"what is number (\d+)", msg, re.IGNORECASE) + if match: + idx = int(match.group(1)) - 1 + if 0 <= idx < len(runner.numbers): + return str(runner.numbers[idx]) + return "" + + monkeypatch.setattr(adk_utils, "run_prompt_async", fake_run_prompt_async) + monkeypatch.setattr(unique_numbers, "run_prompt_async", fake_run_prompt_async) + monkeypatch.setattr( + unique_numbers._UniqueNumbersRunner, + "_maybe_dump_adk_messages_to_file", + lambda *a, **k: None, + ) + + +@pytest.fixture +def patch_adk_utils(monkeypatch): + class FakeAgent: + def __init__(self, instruction: str | None = None, **_): + self.instruction = instruction + + class FakeSession: + def __init__(self, user_id: str, session_id: str = "s1"): + self.user_id = user_id + self.id = session_id + self.events = [] + + class FakeSessionService: + def __init__(self): + self.sessions = {} + + async def create_session(self, app_name: str, user_id: str): + sess = FakeSession(user_id) + self.sessions.setdefault(app_name, {}).setdefault(user_id, {})[sess.id] = ( + sess + ) + return sess + + class FakeRunner: + def __init__( + self, agent=None, app_name="app", session_service=None, responses=None + ): + self.agent = agent + self.app_name = app_name + self.session_service = session_service or FakeSessionService() + self.responses = list(responses or []) + + async def run_async(self, user_id: str, session_id: str, new_message): + session = self.session_service.sessions[self.app_name][user_id][session_id] + session.events.append( + types.SimpleNamespace(author="user", content=new_message) + ) + text = self.responses.pop(0) if self.responses else "ack" + event = types.SimpleNamespace( + author="assistant", + content=adk_utils.types.Content( + parts=[adk_utils.types.Part.from_text(text)] + ), + ) + session.events.append(event) + yield event + + class FakeServerError(Exception): + pass + + monkeypatch.setattr(adk_utils, "Agent", FakeAgent) + monkeypatch.setattr(adk_utils, "Runner", FakeRunner) + monkeypatch.setattr(adk_utils, "InMemorySessionService", FakeSessionService) + monkeypatch.setattr(adk_utils, "ServerError", FakeServerError) + return FakeRunner + + +def test_process_turn_query(patch_unique_numbers): + runner = unique_numbers._UniqueNumbersRunner() + numbers = [1, 2, 3] + metadata = _make_metadata(numbers) + + msg_log = [{"role": "assistant", "content": "What is number 2?"}] + obs, reward, terminated, _, next_meta = runner.process_turn(msg_log, metadata) + + assert obs == {"role": "user", "content": "2"} + assert reward == unique_numbers.PENALTY_FOR_EVERY_ASK + assert not terminated + assert next_meta is not None and next_meta["turn"] == 1 + + +def test_process_turn_correct_guess(patch_unique_numbers): + runner = unique_numbers._UniqueNumbersRunner() + numbers = [1, 2, 1] + metadata = _make_metadata(numbers) + + msg_log = [{"role": "assistant", "content": "There are 2 unique numbers"}] + obs, reward, terminated, _, next_meta = runner.process_turn(msg_log, metadata) + + assert obs == {"role": "user", "content": ""} + assert reward == 1.0 + assert terminated + assert next_meta is None + + +def test_process_turn_incorrect_guess(patch_unique_numbers): + runner = unique_numbers._UniqueNumbersRunner() + numbers = [1, 2, 1] + metadata = _make_metadata(numbers) + + msg_log = [{"role": "assistant", "content": "There are 3 unique numbers"}] + obs, reward, terminated, _, _ = runner.process_turn(msg_log, metadata) + + assert obs["content"] == "" + assert reward == unique_numbers.PENALTY_FOR_INCORRECT_GUESS + assert terminated + + +def test_process_turn_no_message(patch_unique_numbers): + runner = unique_numbers._UniqueNumbersRunner() + numbers = [1, 2, 1] + metadata = _make_metadata(numbers) + + msg_log = [] + obs, reward, terminated, _, _ = runner.process_turn(msg_log, metadata) + + assert obs["content"] == "" + assert reward == unique_numbers.PENALTY_FOR_NO_GUESS + assert terminated + + +def test_run_prompt_async_basic(patch_adk_utils): + agent = adk_utils.Agent(instruction="hi") + runner = adk_utils.Runner(agent=agent, responses=["7"]) + asyncio.run(runner.session_service.create_session("app", "u1")) + out = asyncio.run(adk_utils.run_prompt_async(runner, "u1", "hello", silence=True)) + assert out == "7" + + +def test_run_prompt_async_retry(monkeypatch, patch_adk_utils): + class ErrorRunner(adk_utils.Runner): + def __init__(self): + super().__init__(agent=adk_utils.Agent()) + self.call = 0 + + async def run_async(self, user_id: str, session_id: str, new_message): + self.call += 1 + if self.call == 1: + raise adk_utils.ServerError() + async for e in super().run_async(user_id, session_id, new_message): + yield e + + runner = ErrorRunner() + asyncio.run(runner.session_service.create_session("app", "u2")) + res = asyncio.run( + adk_utils.run_prompt_async( + runner, "u2", "hi", silence=True, max_retries=2, initial_delay=0 + ) + ) + assert res == "ack" + assert runner.call == 2 + + +def test_extract_conversation_history(patch_adk_utils): + agent = adk_utils.Agent(instruction="inst") + runner = adk_utils.Runner(agent=agent, responses=["42"]) + asyncio.run(runner.session_service.create_session("app", "u3")) + asyncio.run(adk_utils.run_prompt_async(runner, "u3", "question", silence=True)) + session_id, convo = adk_utils.extract_conversation_history(runner, "u3") + assert session_id == "s1" + assert convo[0] == {"role": "instruction", "content": "inst"} + assert convo[1]["role"] == "user" + assert convo[2]["content"] == "42" diff --git a/uv.lock b/uv.lock index c2a8c6cfe9..ff230fc2ba 100644 --- a/uv.lock +++ b/uv.lock @@ -33,6 +33,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/04/9d75e1d3bb4ab8ec67ff10919476ccdee06c098bcfcf3a352da5f985171d/absl_py-2.3.0-py3-none-any.whl", hash = "sha256:9824a48b654a306168f63e0d97714665f8490b8d89ec7bf2efc24bf67cf579b3", size = 135657, upload-time = "2025-05-27T09:15:48.742Z" }, ] +[[package]] +name = "absolufy-imports" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/0f/9da9dc9a12ebf4622ec96d9338d221e0172699e7574929f65ec8fdb30f9c/absolufy_imports-0.3.1.tar.gz", hash = "sha256:c90638a6c0b66826d1fb4880ddc20ef7701af34192c94faf40b95d32b59f9793", size = 4724, upload-time = "2022-01-20T14:48:53.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/a4/b65c9fbc2c0c09c0ea3008f62d2010fd261e62a4881502f03a6301079182/absolufy_imports-0.3.1-py2.py3-none-any.whl", hash = "sha256:49bf7c753a9282006d553ba99217f48f947e3eef09e18a700f8a82f75dc7fc5c", size = 5937, upload-time = "2022-01-20T14:48:51.718Z" }, +] + [[package]] name = "accelerate" version = "1.8.1" @@ -235,6 +244,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] +[[package]] +name = "authlib" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" }, +] + [[package]] name = "babel" version = "2.17.0" @@ -1241,6 +1262,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, ] +[[package]] +name = "google-adk" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absolufy-imports" }, + { name = "anyio" }, + { name = "authlib" }, + { name = "click" }, + { name = "fastapi" }, + { name = "google-api-python-client" }, + { name = "google-cloud-aiplatform", extra = ["agent-engines"] }, + { name = "google-cloud-secret-manager" }, + { name = "google-cloud-speech" }, + { name = "google-cloud-storage" }, + { name = "google-genai" }, + { name = "graphviz" }, + { name = "mcp" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-gcp-trace" }, + { name = "opentelemetry-sdk" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, + { name = "starlette" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "tzlocal" }, + { name = "uvicorn" }, + { name = "watchdog" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/1b/1f61a4e6f076d91e9354687d1ea71f61a950512c65eef4382434f7bd948d/google_adk-1.10.0.tar.gz", hash = "sha256:9a35fa6099c1a91c8b970ca98efb80640faad51d93520aa5d7c63ac08ebde8ba", size = 1620716, upload-time = "2025-08-07T16:56:12.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/a6/93819a03ace1457fcb8a811fcc7f3d3ee5546c69bf02078b10ea68d74fa3/google_adk-1.10.0-py3-none-any.whl", hash = "sha256:8f7a41be6cd1cc1d47319335fa799b260a683b2c56644d0a747112c5e0d017ba", size = 1841748, upload-time = "2025-08-07T16:56:11.14Z" }, +] + [[package]] name = "google-api-core" version = "2.25.1" @@ -1257,6 +1318,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807, upload-time = "2025-06-12T20:52:19.334Z" }, ] +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-api-python-client" +version = "2.178.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-auth-httplib2" }, + { name = "httplib2" }, + { name = "uritemplate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/98/916385a87d145a27661b630c480fadf9db32bb1ad9fb1b13e8dbcbe2af70/google_api_python_client-2.178.0.tar.gz", hash = "sha256:99cba921eb471bb5973b780c653ac54d96eef8a42f1b7375b7ab98f257a4414c", size = 13282628, upload-time = "2025-08-06T14:04:51.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/34/8ae31410a2d3f28b16b7135931133caf759d3aa0653f8397e344acec5a88/google_api_python_client-2.178.0-py3-none-any.whl", hash = "sha256:f420adcd050150ff1baefa817e96e1ffa16872744f53471cd34096612e580c34", size = 13809959, upload-time = "2025-08-06T14:04:47.94Z" }, +] + [[package]] name = "google-auth" version = "2.40.3" @@ -1271,6 +1354,264 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, ] +[[package]] +name = "google-auth-httplib2" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "httplib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842, upload-time = "2023-12-12T17:40:30.722Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253, upload-time = "2023-12-12T17:40:13.055Z" }, +] + +[[package]] +name = "google-cloud-aiplatform" +version = "1.108.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docstring-parser" }, + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-bigquery" }, + { name = "google-cloud-resource-manager" }, + { name = "google-cloud-storage" }, + { name = "google-genai" }, + { name = "packaging" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "shapely" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/2f/00393f972b97b7f1505335d632db6ddd6f314b96017887a2f8400f5b24e2/google_cloud_aiplatform-1.108.0.tar.gz", hash = "sha256:ebff9931f948622ea2d34890b2ca8f8f4915c575814fdc1bcc16bc1b1beb5549", size = 9498668, upload-time = "2025-08-08T17:21:21.321Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/09/403d8bddacb33ec67af5a981d6166f391a9c2a2cf163c2a6742bdf958966/google_cloud_aiplatform-1.108.0-py2.py3-none-any.whl", hash = "sha256:2001c3be0d704fe4a1d5adc815172391a0e10a9009e9c3eed721a12d3d431ba6", size = 7895944, upload-time = "2025-08-08T17:21:18.933Z" }, +] + +[package.optional-dependencies] +agent-engines = [ + { name = "cloudpickle" }, + { name = "google-cloud-logging" }, + { name = "google-cloud-trace" }, + { name = "opentelemetry-exporter-gcp-trace" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] + +[[package]] +name = "google-cloud-appengine-logging" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/ea/85da73d4f162b29d24ad591c4ce02688b44094ee5f3d6c0cc533c2b23b23/google_cloud_appengine_logging-1.6.2.tar.gz", hash = "sha256:4890928464c98da9eecc7bf4e0542eba2551512c0265462c10f3a3d2a6424b90", size = 16587, upload-time = "2025-06-11T22:38:53.525Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/9e/dc1fd7f838dcaf608c465171b1a25d8ce63f9987e2d5c73bda98792097a9/google_cloud_appengine_logging-1.6.2-py3-none-any.whl", hash = "sha256:2b28ed715e92b67e334c6fcfe1deb523f001919560257b25fc8fcda95fd63938", size = 16889, upload-time = "2025-06-11T22:38:52.26Z" }, +] + +[[package]] +name = "google-cloud-audit-log" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/af/53b4ef636e492d136b3c217e52a07bee569430dda07b8e515d5f2b701b1e/google_cloud_audit_log-0.3.2.tar.gz", hash = "sha256:2598f1533a7d7cdd6c7bf448c12e5519c1d53162d78784e10bcdd1df67791bc3", size = 33377, upload-time = "2025-03-17T11:27:59.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/74/38a70339e706b174b3c1117ad931aaa0ff0565b599869317a220d1967e1b/google_cloud_audit_log-0.3.2-py3-none-any.whl", hash = "sha256:daaedfb947a0d77f524e1bd2b560242ab4836fe1afd6b06b92f152b9658554ed", size = 32472, upload-time = "2025-03-17T11:27:58.51Z" }, +] + +[[package]] +name = "google-cloud-bigquery" +version = "3.35.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-resumable-media" }, + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/e4/9cf03fa81fefd1b9811a7cd6e398804ae0de3b6a4edef810e2acd45cabbc/google_cloud_bigquery-3.35.1.tar.gz", hash = "sha256:599f26cacf190acfe88000f6cc5f4bc9e6baac7899e4f406ca054f1906f71960", size = 496433, upload-time = "2025-07-24T15:09:04.108Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/50/96fe9bc5b83d3a421e91ed8edc2535de45957e9af398273e3ecb5c3a1094/google_cloud_bigquery-3.35.1-py3-none-any.whl", hash = "sha256:6739a6ba63c6d80735ca2b34b1df2090ff473b80c1a62354caa2debe6dbbd961", size = 256877, upload-time = "2025-07-24T15:09:02.443Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861, upload-time = "2025-03-10T21:05:38.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348, upload-time = "2025-03-10T21:05:37.785Z" }, +] + +[[package]] +name = "google-cloud-logging" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-appengine-logging" }, + { name = "google-cloud-audit-log" }, + { name = "google-cloud-core" }, + { name = "grpc-google-iam-v1" }, + { name = "opentelemetry-api" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/9c/d42ecc94f795a6545930e5f846a7ae59ff685ded8bc086648dd2bee31a1a/google_cloud_logging-3.12.1.tar.gz", hash = "sha256:36efc823985055b203904e83e1c8f9f999b3c64270bcda39d57386ca4effd678", size = 289569, upload-time = "2025-04-22T20:50:24.71Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/41/f8a3197d39b773a91f335dee36c92ef26a8ec96efe78d64baad89d367df4/google_cloud_logging-3.12.1-py2.py3-none-any.whl", hash = "sha256:6817878af76ec4e7568976772839ab2c43ddfd18fbbf2ce32b13ef549cd5a862", size = 229466, upload-time = "2025-04-22T20:50:23.294Z" }, +] + +[[package]] +name = "google-cloud-resource-manager" +version = "1.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/ca/a4648f5038cb94af4b3942815942a03aa9398f9fb0bef55b3f1585b9940d/google_cloud_resource_manager-1.14.2.tar.gz", hash = "sha256:962e2d904c550d7bac48372607904ff7bb3277e3bb4a36d80cc9a37e28e6eb74", size = 446370, upload-time = "2025-03-17T11:35:56.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/ea/a92631c358da377af34d3a9682c97af83185c2d66363d5939ab4a1169a7f/google_cloud_resource_manager-1.14.2-py3-none-any.whl", hash = "sha256:d0fa954dedd1d2b8e13feae9099c01b8aac515b648e612834f9942d2795a9900", size = 394344, upload-time = "2025-03-17T11:35:54.722Z" }, +] + +[[package]] +name = "google-cloud-secret-manager" +version = "2.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/7a/2fa6735ec693d822fe08a76709c4d95d9b5b4c02e83e720497355039d2ee/google_cloud_secret_manager-2.24.0.tar.gz", hash = "sha256:ce573d40ffc2fb7d01719243a94ee17aa243ea642a6ae6c337501e58fbf642b5", size = 269516, upload-time = "2025-06-05T22:22:22.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/af/db1217cae1809e69a4527ee6293b82a9af2a1fb2313ad110c775e8f3c820/google_cloud_secret_manager-2.24.0-py3-none-any.whl", hash = "sha256:9bea1254827ecc14874bc86c63b899489f8f50bfe1442bfb2517530b30b3a89b", size = 218050, upload-time = "2025-06-10T02:02:19.88Z" }, +] + +[[package]] +name = "google-cloud-speech" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/74/9c5a556f8af19cab461058aa15e1409e7afa453ca2383473a24a12801ef7/google_cloud_speech-2.33.0.tar.gz", hash = "sha256:fd08511b5124fdaa768d71a4054e84a5d8eb02531cb6f84f311c0387ea1314ed", size = 389072, upload-time = "2025-06-11T23:56:37.231Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/1d/880342b2541b4bad888ad8ab2ac77d4b5dad25b32a2a1c5f21140c14c8e3/google_cloud_speech-2.33.0-py3-none-any.whl", hash = "sha256:4ba16c8517c24a6abcde877289b0f40b719090504bf06b1adea248198ccd50a5", size = 335681, upload-time = "2025-06-11T23:56:36.026Z" }, +] + +[[package]] +name = "google-cloud-storage" +version = "2.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "google-resumable-media" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/76/4d965702e96bb67976e755bed9828fa50306dca003dbee08b67f41dd265e/google_cloud_storage-2.19.0.tar.gz", hash = "sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2", size = 5535488, upload-time = "2024-12-05T01:35:06.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/94/6db383d8ee1adf45dc6c73477152b82731fa4c4a46d9c1932cc8757e0fd4/google_cloud_storage-2.19.0-py2.py3-none-any.whl", hash = "sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba", size = 131787, upload-time = "2024-12-05T01:35:04.736Z" }, +] + +[[package]] +name = "google-cloud-trace" +version = "1.16.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/ea/0e42e2196fb2bc8c7b25f081a0b46b5053d160b34d5322e7eac2d5f7a742/google_cloud_trace-1.16.2.tar.gz", hash = "sha256:89bef223a512465951eb49335be6d60bee0396d576602dbf56368439d303cab4", size = 97826, upload-time = "2025-06-12T00:53:02.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/96/7a8d271e91effa9ccc2fd7cfd5cf287a2d7900080a475477c2ac0c7a331d/google_cloud_trace-1.16.2-py3-none-any.whl", hash = "sha256:40fb74607752e4ee0f3d7e5fc6b8f6eb1803982254a1507ba918172484131456", size = 103755, upload-time = "2025-06-12T00:53:00.672Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470, upload-time = "2025-03-26T14:34:31.655Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315, upload-time = "2025-03-26T15:01:54.634Z" }, + { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180, upload-time = "2025-03-26T14:41:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794, upload-time = "2025-03-26T14:41:33.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477, upload-time = "2025-03-26T14:29:10.94Z" }, + { url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467, upload-time = "2025-03-26T14:36:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309, upload-time = "2025-03-26T15:06:15.318Z" }, + { url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133, upload-time = "2025-03-26T14:41:34.388Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773, upload-time = "2025-03-26T14:41:35.19Z" }, + { url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475, upload-time = "2025-03-26T14:29:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243, upload-time = "2025-03-26T14:41:35.975Z" }, + { url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870, upload-time = "2025-03-26T14:41:37.08Z" }, +] + +[[package]] +name = "google-genai" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "google-auth" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/9b/a1e31252c4151da9403b357b47ae7ec5fc852eaf3486696eec211794001d/google_genai-1.29.0.tar.gz", hash = "sha256:a6b036ab032830f668d137b198c2a5abd8951a036d7a8480b61ce837c1c7f36b", size = 224207, upload-time = "2025-08-06T23:32:09.708Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/33/9b22b0b3734f93655d0d28cfcd64496ef46dd68efe8ae19278f3b1297998/google_genai-1.29.0-py3-none-any.whl", hash = "sha256:8b64737de008d15ca4737e593913f88f656f0568544ab6901f768f0d1fd69bbf", size = 222591, upload-time = "2025-08-06T23:32:08.133Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099, upload-time = "2024-08-07T22:20:38.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251, upload-time = "2024-08-07T22:20:36.409Z" }, +] + [[package]] name = "googleapis-common-protos" version = "1.70.0" @@ -1283,6 +1624,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, ] +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, +] + [[package]] name = "graphene" version = "3.4.3" @@ -1361,6 +1707,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, ] +[[package]] +name = "grpc-google-iam-v1" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", extra = ["grpc"] }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/4e/8d0ca3b035e41fe0b3f31ebbb638356af720335e5a11154c330169b40777/grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20", size = 16259, upload-time = "2025-03-17T11:40:23.586Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/6f/dd9b178aee7835b96c2e63715aba6516a9d50f6bebbd1cc1d32c82a2a6c3/grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351", size = 19242, upload-time = "2025-03-17T11:40:22.648Z" }, +] + [[package]] name = "grpcio" version = "1.73.0" @@ -1389,6 +1749,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/35/347db7d2e7674b621afd21b12022e7f48c7b0861b5577134b4e939536141/grpcio-1.73.0-cp313-cp313-win_amd64.whl", hash = "sha256:38cf518cc54cd0c47c9539cefa8888549fcc067db0b0c66a46535ca8032020c4", size = 4335872, upload-time = "2025-06-09T10:04:29.032Z" }, ] +[[package]] +name = "grpcio-status" +version = "1.71.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/d1/b6e9877fedae3add1afdeae1f89d1927d296da9cf977eca0eb08fb8a460e/grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50", size = 13677, upload-time = "2025-06-28T04:24:05.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/58/317b0134129b556a93a3b0afe00ee675b5657f0155509e22fcb853bafe2d/grpcio_status-1.71.2-py3-none-any.whl", hash = "sha256:803c98cb6a8b7dc6dbb785b1111aed739f241ab5e9da0bba96888aa74704cfd3", size = 14424, upload-time = "2025-06-28T04:23:42.136Z" }, +] + [[package]] name = "gunicorn" version = "23.0.0" @@ -1474,6 +1848,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httplib2" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116, upload-time = "2023-03-21T22:29:37.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854, upload-time = "2023-03-21T22:29:35.683Z" }, +] + [[package]] name = "httptools" version = "0.6.4" @@ -1511,6 +1897,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "httpx-sse" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998, upload-time = "2025-06-24T13:21:05.71Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054, upload-time = "2025-06-24T13:21:04.772Z" }, +] + [[package]] name = "huggingface-hub" version = "0.34.2" @@ -2131,6 +2526,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, ] +[[package]] +name = "mcp" +version = "1.12.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/88/f6cb7e7c260cd4b4ce375f2b1614b33ce401f63af0f49f7141a2e9bf0a45/mcp-1.12.4.tar.gz", hash = "sha256:0765585e9a3a5916a3c3ab8659330e493adc7bd8b2ca6120c2d7a0c43e034ca5", size = 431148, upload-time = "2025-08-07T20:31:18.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/68/316cbc54b7163fa22571dcf42c9cc46562aae0a021b974e0a8141e897200/mcp-1.12.4-py3-none-any.whl", hash = "sha256:7aa884648969fab8e78b89399d59a683202972e12e6bc9a1c88ce7eda7743789", size = 160145, upload-time = "2025-08-07T20:31:15.69Z" }, +] + [[package]] name = "mdit-py-plugins" version = "0.4.2" @@ -2541,6 +2958,9 @@ dependencies = [ ] [package.optional-dependencies] +adk = [ + { name = "google-adk" }, +] automodel = [ { name = "flash-attn" }, ] @@ -2598,6 +3018,7 @@ requires-dist = [ { name = "flash-attn", marker = "extra == 'automodel'", specifier = "==2.7.4.post1" }, { name = "flash-attn", marker = "extra == 'mcore'", specifier = "==2.7.4.post1" }, { name = "flash-attn", marker = "extra == 'vllm'", specifier = "==2.7.4.post1" }, + { name = "google-adk", marker = "extra == 'adk'" }, { name = "hydra-core" }, { name = "math-verify" }, { name = "matplotlib" }, @@ -2624,7 +3045,7 @@ requires-dist = [ { name = "vllm", marker = "extra == 'vllm'", specifier = "==0.10.0" }, { name = "wandb" }, ] -provides-extras = ["automodel", "vllm", "mcore"] +provides-extras = ["automodel", "vllm", "mcore", "adk"] [package.metadata.requires-dev] build = [ @@ -3170,6 +3591,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a5/3a/2ba85557e8dc024c0842ad22c570418dc02c36cbd1ab4b832a93edf071b8/opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c", size = 65767, upload-time = "2025-06-10T08:54:56.717Z" }, ] +[[package]] +name = "opentelemetry-exporter-gcp-trace" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-cloud-trace" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-resourcedetector-gcp" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/15/7556d54b01fb894497f69a98d57faa9caa45ffa59896e0bba6847a7f0d15/opentelemetry_exporter_gcp_trace-1.9.0.tar.gz", hash = "sha256:c3fc090342f6ee32a0cc41a5716a6bb716b4422d19facefcb22dc4c6b683ece8", size = 18568, upload-time = "2025-02-04T19:45:08.185Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/cd/6d7fbad05771eb3c2bace20f6360ce5dac5ca751c6f2122853e43830c32e/opentelemetry_exporter_gcp_trace-1.9.0-py3-none-any.whl", hash = "sha256:0a8396e8b39f636eeddc3f0ae08ddb40c40f288bc8c5544727c3581545e77254", size = 13973, upload-time = "2025-02-04T19:44:59.148Z" }, +] + +[[package]] +name = "opentelemetry-resourcedetector-gcp" +version = "1.9.0a0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/86/f0693998817779802525a5bcc885a3cdb68d05b636bc6faae5c9ade4bee4/opentelemetry_resourcedetector_gcp-1.9.0a0.tar.gz", hash = "sha256:6860a6649d1e3b9b7b7f09f3918cc16b72aa0c0c590d2a72ea6e42b67c9a42e7", size = 20730, upload-time = "2025-02-04T19:45:10.693Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/04/7e33228c88422a5518e1774a836c9ec68f10f51bde0f1d5dd5f3054e612a/opentelemetry_resourcedetector_gcp-1.9.0a0-py3-none-any.whl", hash = "sha256:4e5a0822b0f0d7647b7ceb282d7aa921dd7f45466540bd0a24f954f90db8fde8", size = 20378, upload-time = "2025-02-04T19:45:03.898Z" }, +] + [[package]] name = "opentelemetry-sdk" version = "1.34.1" @@ -3857,6 +4308,20 @@ pycountry = [ { name = "pycountry" }, ] +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + [[package]] name = "pydata-sphinx-theme" version = "0.16.1" @@ -4583,6 +5048,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/6d/b4752b044bf94cb802d88a888dc7d288baaf77d7910b7dedda74b5ceea0c/setuptools-79.0.1-py3-none-any.whl", hash = "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51", size = 1256281, upload-time = "2025-04-23T22:20:56.768Z" }, ] +[[package]] +name = "shapely" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/64/9544dc07dfe80a2d489060791300827c941c451e2910f7364b19607ea352/shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43", size = 1833021, upload-time = "2025-05-19T11:04:08.022Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/fb5f545e72e89b6a0f04a0effda144f5be956c9c312c7d4e00dfddbddbcf/shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad", size = 1643018, upload-time = "2025-05-19T11:04:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/03/46/61e03edba81de729f09d880ce7ae5c1af873a0814206bbfb4402ab5c3388/shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9", size = 2986417, upload-time = "2025-05-19T11:04:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/1f/1e/83ec268ab8254a446b4178b45616ab5822d7b9d2b7eb6e27cf0b82f45601/shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef", size = 3098224, upload-time = "2025-05-19T11:04:11.903Z" }, + { url = "https://files.pythonhosted.org/packages/f1/44/0c21e7717c243e067c9ef8fa9126de24239f8345a5bba9280f7bb9935959/shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1", size = 3925982, upload-time = "2025-05-19T11:04:13.224Z" }, + { url = "https://files.pythonhosted.org/packages/15/50/d3b4e15fefc103a0eb13d83bad5f65cd6e07a5d8b2ae920e767932a247d1/shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d", size = 4089122, upload-time = "2025-05-19T11:04:14.477Z" }, + { url = "https://files.pythonhosted.org/packages/bd/05/9a68f27fc6110baeedeeebc14fd86e73fa38738c5b741302408fb6355577/shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8", size = 1522437, upload-time = "2025-05-19T11:04:16.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e9/a4560e12b9338842a1f82c9016d2543eaa084fce30a1ca11991143086b57/shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a", size = 1703479, upload-time = "2025-05-19T11:04:18.497Z" }, + { url = "https://files.pythonhosted.org/packages/71/8e/2bc836437f4b84d62efc1faddce0d4e023a5d990bbddd3c78b2004ebc246/shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48", size = 1832107, upload-time = "2025-05-19T11:04:19.736Z" }, + { url = "https://files.pythonhosted.org/packages/12/a2/12c7cae5b62d5d851c2db836eadd0986f63918a91976495861f7c492f4a9/shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6", size = 1642355, upload-time = "2025-05-19T11:04:21.035Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/6d28b43d53fea56de69c744e34c2b999ed4042f7a811dc1bceb876071c95/shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c", size = 2968871, upload-time = "2025-05-19T11:04:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/1017c31e52370b2b79e4d29e07cbb590ab9e5e58cf7e2bdfe363765d6251/shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a", size = 3080830, upload-time = "2025-05-19T11:04:23.997Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fe/f4a03d81abd96a6ce31c49cd8aaba970eaaa98e191bd1e4d43041e57ae5a/shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de", size = 3908961, upload-time = "2025-05-19T11:04:25.702Z" }, + { url = "https://files.pythonhosted.org/packages/ef/59/7605289a95a6844056a2017ab36d9b0cb9d6a3c3b5317c1f968c193031c9/shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8", size = 4079623, upload-time = "2025-05-19T11:04:27.171Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4d/9fea036eff2ef4059d30247128b2d67aaa5f0b25e9fc27e1d15cc1b84704/shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52", size = 1521916, upload-time = "2025-05-19T11:04:28.405Z" }, + { url = "https://files.pythonhosted.org/packages/12/d9/6d13b8957a17c95794f0c4dfb65ecd0957e6c7131a56ce18d135c1107a52/shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97", size = 1702746, upload-time = "2025-05-19T11:04:29.643Z" }, + { url = "https://files.pythonhosted.org/packages/60/36/b1452e3e7f35f5f6454d96f3be6e2bb87082720ff6c9437ecc215fa79be0/shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d", size = 1833482, upload-time = "2025-05-19T11:04:30.852Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ca/8e6f59be0718893eb3e478141285796a923636dc8f086f83e5b0ec0036d0/shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05", size = 1642256, upload-time = "2025-05-19T11:04:32.068Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/0053aea449bb1d4503999525fec6232f049abcdc8df60d290416110de943/shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0", size = 3016614, upload-time = "2025-05-19T11:04:33.7Z" }, + { url = "https://files.pythonhosted.org/packages/ee/53/36f1b1de1dfafd1b457dcbafa785b298ce1b8a3e7026b79619e708a245d5/shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913", size = 3093542, upload-time = "2025-05-19T11:04:34.952Z" }, + { url = "https://files.pythonhosted.org/packages/b9/bf/0619f37ceec6b924d84427c88835b61f27f43560239936ff88915c37da19/shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d", size = 3945961, upload-time = "2025-05-19T11:04:36.32Z" }, + { url = "https://files.pythonhosted.org/packages/93/c9/20ca4afeb572763b07a7997f00854cb9499df6af85929e93012b189d8917/shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9", size = 4089514, upload-time = "2025-05-19T11:04:37.683Z" }, + { url = "https://files.pythonhosted.org/packages/33/6a/27036a5a560b80012a544366bceafd491e8abb94a8db14047b5346b5a749/shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db", size = 1540607, upload-time = "2025-05-19T11:04:38.925Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -4846,6 +5346,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, ] +[[package]] +name = "sse-starlette" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" }, +] + [[package]] name = "starlette" version = "0.46.2" @@ -4879,6 +5391,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + [[package]] name = "tensorboard" version = "2.19.0" @@ -5352,6 +5873,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + +[[package]] +name = "uritemplate" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, +] + [[package]] name = "urllib3" version = "1.26.20" @@ -5525,6 +6067,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/5f/c44ad7b2a062ca5f4da99ae475cea274c38f6ec37bdaca1b1c653ee87274/wandb-0.20.1-py3-none-win_amd64.whl", hash = "sha256:6d2431652f096b7e394c29a99135a6441c02ed3198b963f0b351a5b5e56aeca0", size = 22518459, upload-time = "2025-06-05T00:00:21.374Z" }, ] +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + [[package]] name = "watchfiles" version = "1.1.0"