From 2899b5f8482390f1e8fc9a71ac7b24f6c21d319d Mon Sep 17 00:00:00 2001 From: Ariel Frischer Date: Mon, 10 Mar 2025 04:08:12 -0700 Subject: [PATCH 01/14] feat(agent_utils.py): add AnthropicCallbackHandler to track token usage and costs for Anthropic models (#118) style(agent_utils.py): format imports and code for better readability refactor(agent_utils.py): standardize model name and cost calculation logic for clarity and maintainability chore(anthropic_callback_handler.py): create a new file for the AnthropicCallbackHandler implementation and related functions --- ra_aid/agent_utils.py | 483 ++++++---- .../callbacks/anthropic_callback_handler.py | 270 ++++++ uv.lock | 910 +++++++++--------- 3 files changed, 1040 insertions(+), 623 deletions(-) create mode 100644 ra_aid/callbacks/anthropic_callback_handler.py diff --git a/ra_aid/agent_utils.py b/ra_aid/agent_utils.py index 59aee41..2593f13 100644 --- a/ra_aid/agent_utils.py +++ b/ra_aid/agent_utils.py @@ -10,6 +10,9 @@ import uuid from datetime import datetime from typing import Any, Dict, List, Literal, Optional, Sequence +from ra_aid.callbacks.anthropic_callback_handler import AnthropicCallbackHandler + + import litellm from anthropic import APIError, APITimeoutError, InternalServerError, RateLimitError from openai import RateLimitError as OpenAIRateLimitError @@ -71,7 +74,11 @@ from ra_aid.prompts.human_prompts import ( from ra_aid.prompts.implementation_prompts import IMPLEMENTATION_PROMPT from ra_aid.prompts.common_prompts import NEW_PROJECT_HINTS from ra_aid.prompts.planning_prompts import PLANNING_PROMPT -from ra_aid.prompts.reasoning_assist_prompt import REASONING_ASSIST_PROMPT_PLANNING, REASONING_ASSIST_PROMPT_IMPLEMENTATION, REASONING_ASSIST_PROMPT_RESEARCH +from ra_aid.prompts.reasoning_assist_prompt import ( + REASONING_ASSIST_PROMPT_PLANNING, + REASONING_ASSIST_PROMPT_IMPLEMENTATION, + REASONING_ASSIST_PROMPT_RESEARCH, +) from ra_aid.prompts.research_prompts import ( RESEARCH_ONLY_PROMPT, RESEARCH_PROMPT, @@ -90,9 +97,15 @@ from ra_aid.tool_configs import ( ) from ra_aid.tools.handle_user_defined_test_cmd_execution import execute_test_command from ra_aid.database.repositories.key_fact_repository import get_key_fact_repository -from ra_aid.database.repositories.key_snippet_repository import get_key_snippet_repository -from ra_aid.database.repositories.human_input_repository import get_human_input_repository -from ra_aid.database.repositories.research_note_repository import get_research_note_repository +from ra_aid.database.repositories.key_snippet_repository import ( + get_key_snippet_repository, +) +from ra_aid.database.repositories.human_input_repository import ( + get_human_input_repository, +) +from ra_aid.database.repositories.research_note_repository import ( + get_research_note_repository, +) from ra_aid.database.repositories.work_log_repository import get_work_log_repository from ra_aid.model_formatters import format_key_facts_dict from ra_aid.model_formatters.key_snippets_formatter import format_key_snippets_dict @@ -332,7 +345,9 @@ def create_agent( if is_anthropic_claude(config): logger.debug("Using create_react_agent to instantiate agent.") agent_kwargs = build_agent_kwargs(checkpointer, max_input_tokens) - return create_react_agent(model, tools, interrupt_after=['tools'], **agent_kwargs) + return create_react_agent( + model, tools, interrupt_after=["tools"], **agent_kwargs + ) else: logger.debug("Using CiaynAgent agent instance") return CiaynAgent(model, tools, max_tokens=max_input_tokens, config=config) @@ -343,7 +358,9 @@ def create_agent( config = get_config_repository().get_all() max_input_tokens = get_model_token_limit(config, agent_type) agent_kwargs = build_agent_kwargs(checkpointer, max_input_tokens) - return create_react_agent(model, tools, interrupt_after=['tools'], **agent_kwargs) + return create_react_agent( + model, tools, interrupt_after=["tools"], **agent_kwargs + ) def run_research_agent( @@ -406,7 +423,9 @@ def run_research_agent( recent_inputs = human_input_repository.get_recent(1) if recent_inputs and len(recent_inputs) > 0: last_human_input = recent_inputs[0].content - base_task = f"{last_human_input}\n{base_task}" + base_task = ( + f"{last_human_input}\n{base_task}" + ) except RuntimeError as e: logger.error(f"Failed to access human input repository: {str(e)}") # Continue without appending last human input @@ -416,7 +435,9 @@ def run_research_agent( except RuntimeError as e: logger.error(f"Failed to access key fact repository: {str(e)}") key_facts = "" - key_snippets = format_key_snippets_dict(get_key_snippet_repository().get_snippets_dict()) + key_snippets = format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ) related_files = get_related_files() try: @@ -432,20 +453,22 @@ def run_research_agent( human_interaction=hil, web_research_enabled=get_config_repository().get("web_research_enabled", False), ) - + # Get model info for reasoning assistance configuration provider = get_config_repository().get("provider", "") model_name = get_config_repository().get("model", "") - + # Get model configuration to check for reasoning_assist_default model_config = {} provider_models = models_params.get(provider, {}) if provider_models and model_name in provider_models: model_config = provider_models[model_name] - + # Check if reasoning assist is explicitly enabled/disabled force_assistance = get_config_repository().get("force_reasoning_assistance", False) - disable_assistance = get_config_repository().get("disable_reasoning_assistance", False) + disable_assistance = get_config_repository().get( + "disable_reasoning_assistance", False + ) if force_assistance: reasoning_assist_enabled = True elif disable_assistance: @@ -453,26 +476,31 @@ def run_research_agent( else: # Fall back to model default reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) - + logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) expert_guidance = "" - + # Get research note information for reasoning assistance try: - research_notes = format_research_notes_dict(get_research_note_repository().get_notes_dict()) + research_notes = format_research_notes_dict( + get_research_note_repository().get_notes_dict() + ) except Exception as e: logger.warning(f"Failed to get research notes: {e}") research_notes = "" - + # If reasoning assist is enabled, make a one-off call to the expert model if reasoning_assist_enabled: try: - logger.info("Reasoning assist enabled for model %s, getting expert guidance", model_name) - + logger.info( + "Reasoning assist enabled for model %s, getting expert guidance", + model_name, + ) + # Collect tool descriptions tool_metadata = [] from ra_aid.tools.reflection import get_function_info as get_tool_info - + for tool in tools: try: tool_info = get_tool_info(tool.func) @@ -481,13 +509,13 @@ def run_research_agent( tool_metadata.append(f"Tool: {name}\nDescription: {description}\n") except Exception as e: logger.warning(f"Error getting tool info for {tool}: {e}") - + # Format tool metadata formatted_tool_metadata = "\n".join(tool_metadata) - + # Initialize expert model expert_model = initialize_expert_llm(provider, model_name) - + # Format the reasoning assist prompt reasoning_assist_prompt = REASONING_ASSIST_PROMPT_RESEARCH.format( current_date=current_date, @@ -500,62 +528,78 @@ def run_research_agent( env_inv=get_env_inv(), tool_metadata=formatted_tool_metadata, ) - + # Show the reasoning assist query in a panel console.print( - Panel(Markdown("Consulting with the reasoning model on the best research approach."), title="📝 Thinking about research strategy...", border_style="yellow") + Panel( + Markdown( + "Consulting with the reasoning model on the best research approach." + ), + title="📝 Thinking about research strategy...", + border_style="yellow", + ) ) - + logger.debug("Invoking expert model for reasoning assist") # Make the call to the expert model response = expert_model.invoke(reasoning_assist_prompt) - + # Check if the model supports think tags supports_think_tag = model_config.get("supports_think_tag", False) supports_thinking = model_config.get("supports_thinking", False) - + # Get response content, handling if it's a list (for Claude thinking mode) content = None - - if hasattr(response, 'content'): + + if hasattr(response, "content"): content = response.content else: # Fallback if content attribute is missing content = str(response) - + # Process content based on its type if isinstance(content, list): # Handle structured thinking mode (e.g., Claude 3.7) thinking_content = None response_text = None - + # Process each item in the list for item in content: if isinstance(item, dict): # Extract thinking content - if item.get('type') == 'thinking' and 'thinking' in item: - thinking_content = item['thinking'] + if item.get("type") == "thinking" and "thinking" in item: + thinking_content = item["thinking"] logger.debug("Found structured thinking content") # Extract response text - elif item.get('type') == 'text' and 'text' in item: - response_text = item['text'] + elif item.get("type") == "text" and "text" in item: + response_text = item["text"] logger.debug("Found structured response text") - + # Display thinking content in a separate panel if available - if thinking_content and get_config_repository().get("show_thoughts", False): - logger.debug(f"Displaying structured thinking content ({len(thinking_content)} chars)") - console.print( - Panel(Markdown(thinking_content), title="💭 Expert Thinking", border_style="yellow") + if thinking_content and get_config_repository().get( + "show_thoughts", False + ): + logger.debug( + f"Displaying structured thinking content ({len(thinking_content)} chars)" ) - + console.print( + Panel( + Markdown(thinking_content), + title="💭 Expert Thinking", + border_style="yellow", + ) + ) + # Use response_text if available, otherwise fall back to joining if response_text: content = response_text else: # Fallback: join list items if structured extraction failed - logger.debug("No structured response text found, joining list items") + logger.debug( + "No structured response text found, joining list items" + ) content = "\n".join(str(item) for item in content) - elif (supports_think_tag or supports_thinking): + elif supports_think_tag or supports_thinking: # Process thinking content using the centralized function content, _ = process_thinking_content( content=content, @@ -563,22 +607,28 @@ def run_research_agent( supports_thinking=supports_thinking, panel_title="💭 Expert Thinking", panel_style="yellow", - logger=logger + logger=logger, ) - + # Display the expert guidance in a panel console.print( - Panel(Markdown(content), title="Research Strategy Guidance", border_style="blue") + Panel( + Markdown(content), + title="Research Strategy Guidance", + border_style="blue", + ) ) - + # Use the content as expert guidance - expert_guidance = content + "\n\nCONSULT WITH THE EXPERT FREQUENTLY DURING RESEARCH" - + expert_guidance = ( + content + "\n\nCONSULT WITH THE EXPERT FREQUENTLY DURING RESEARCH" + ) + logger.info("Received expert guidance for research") except Exception as e: logger.error("Error getting expert guidance for research: %s", e) expert_guidance = "" - + agent = create_agent(model, tools, checkpointer=memory, agent_type="research") expert_section = EXPERT_PROMPT_SECTION_RESEARCH if expert_enabled else "" @@ -588,7 +638,7 @@ def run_research_agent( if get_config_repository().get("web_research_enabled") else "" ) - + # Prepare expert guidance section if expert guidance is available expert_guidance_section = "" if expert_guidance: @@ -600,7 +650,7 @@ def run_research_agent( # We get research notes earlier for reasoning assistance # Get environment inventory information - + prompt = (RESEARCH_ONLY_PROMPT if research_only else RESEARCH_PROMPT).format( current_date=current_date, working_directory=working_directory, @@ -643,9 +693,7 @@ def run_research_agent( if agent is not None: logger.debug("Research agent created successfully") none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry( - agent, prompt, none_or_fallback_handler - ) + _result = run_agent_with_retry(agent, prompt, none_or_fallback_handler) if _result: # Log research completion log_work_event(f"Completed research phase for: {base_task_or_query}") @@ -731,7 +779,9 @@ def run_web_research_agent( logger.error(f"Failed to access key fact repository: {str(e)}") key_facts = "" try: - key_snippets = format_key_snippets_dict(get_key_snippet_repository().get_snippets_dict()) + key_snippets = format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ) except RuntimeError as e: logger.error(f"Failed to access key snippet repository: {str(e)}") key_snippets = "" @@ -741,7 +791,7 @@ def run_web_research_agent( working_directory = os.getcwd() # Get environment inventory information - + prompt = WEB_RESEARCH_PROMPT.format( current_date=current_date, working_directory=working_directory, @@ -771,9 +821,7 @@ def run_web_research_agent( logger.debug("Web research agent completed successfully") none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry( - agent, prompt, none_or_fallback_handler - ) + _result = run_agent_with_retry(agent, prompt, none_or_fallback_handler) if _result: # Log web research completion log_work_event(f"Completed web research phase for: {query}") @@ -835,17 +883,19 @@ def run_planning_agent( provider = get_config_repository().get("provider", "") model_name = get_config_repository().get("model", "") logger.debug("Checking for reasoning_assist_default on %s/%s", provider, model_name) - + # Get model configuration to check for reasoning_assist_default model_config = {} provider_models = models_params.get(provider, {}) if provider_models and model_name in provider_models: model_config = provider_models[model_name] - + # Check if reasoning assist is explicitly enabled/disabled force_assistance = get_config_repository().get("force_reasoning_assistance", False) - disable_assistance = get_config_repository().get("disable_reasoning_assistance", False) - + disable_assistance = get_config_repository().get( + "disable_reasoning_assistance", False + ) + if force_assistance: reasoning_assist_enabled = True elif disable_assistance: @@ -853,27 +903,29 @@ def run_planning_agent( else: # Fall back to model default reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) - + logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) - + # Get all the context information (used both for normal planning and reasoning assist) current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") working_directory = os.getcwd() - + # Make sure key_facts is defined before using it try: key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) except RuntimeError as e: logger.error(f"Failed to access key fact repository: {str(e)}") key_facts = "" - + # Make sure key_snippets is defined before using it try: - key_snippets = format_key_snippets_dict(get_key_snippet_repository().get_snippets_dict()) + key_snippets = format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ) except RuntimeError as e: logger.error(f"Failed to access key snippet repository: {str(e)}") key_snippets = "" - + # Get formatted research notes using repository try: repository = get_research_note_repository() @@ -882,28 +934,31 @@ def run_planning_agent( except RuntimeError as e: logger.error(f"Failed to access research note repository: {str(e)}") formatted_research_notes = "" - + # Get related files related_files = "\n".join(get_related_files()) - + # Get environment inventory information env_inv = get_env_inv() - + # Display the planning stage header before any reasoning assistance print_stage_header("Planning Stage") - + # Initialize expert guidance section expert_guidance = "" - + # If reasoning assist is enabled, make a one-off call to the expert model if reasoning_assist_enabled: try: - logger.info("Reasoning assist enabled for model %s, getting expert guidance", model_name) - + logger.info( + "Reasoning assist enabled for model %s, getting expert guidance", + model_name, + ) + # Collect tool descriptions tool_metadata = [] from ra_aid.tools.reflection import get_function_info as get_tool_info - + for tool in tools: try: tool_info = get_tool_info(tool.func) @@ -912,13 +967,13 @@ def run_planning_agent( tool_metadata.append(f"Tool: {name}\nDescription: {description}\n") except Exception as e: logger.warning(f"Error getting tool info for {tool}: {e}") - + # Format tool metadata formatted_tool_metadata = "\n".join(tool_metadata) - + # Initialize expert model expert_model = initialize_expert_llm(provider, model_name) - + # Format the reasoning assist prompt reasoning_assist_prompt = REASONING_ASSIST_PROMPT_PLANNING.format( current_date=current_date, @@ -931,62 +986,78 @@ def run_planning_agent( env_inv=env_inv, tool_metadata=formatted_tool_metadata, ) - + # Show the reasoning assist query in a panel console.print( - Panel(Markdown("Consulting with the reasoning model on the best way to do this."), title="📝 Thinking about the plan...", border_style="yellow") + Panel( + Markdown( + "Consulting with the reasoning model on the best way to do this." + ), + title="📝 Thinking about the plan...", + border_style="yellow", + ) ) - + logger.debug("Invoking expert model for reasoning assist") # Make the call to the expert model response = expert_model.invoke(reasoning_assist_prompt) - + # Check if the model supports think tags supports_think_tag = model_config.get("supports_think_tag", False) supports_thinking = model_config.get("supports_thinking", False) - + # Get response content, handling if it's a list (for Claude thinking mode) content = None - - if hasattr(response, 'content'): + + if hasattr(response, "content"): content = response.content else: # Fallback if content attribute is missing content = str(response) - + # Process content based on its type if isinstance(content, list): # Handle structured thinking mode (e.g., Claude 3.7) thinking_content = None response_text = None - + # Process each item in the list for item in content: if isinstance(item, dict): # Extract thinking content - if item.get('type') == 'thinking' and 'thinking' in item: - thinking_content = item['thinking'] + if item.get("type") == "thinking" and "thinking" in item: + thinking_content = item["thinking"] logger.debug("Found structured thinking content") # Extract response text - elif item.get('type') == 'text' and 'text' in item: - response_text = item['text'] + elif item.get("type") == "text" and "text" in item: + response_text = item["text"] logger.debug("Found structured response text") - + # Display thinking content in a separate panel if available - if thinking_content and get_config_repository().get("show_thoughts", False): - logger.debug(f"Displaying structured thinking content ({len(thinking_content)} chars)") - console.print( - Panel(Markdown(thinking_content), title="💭 Expert Thinking", border_style="yellow") + if thinking_content and get_config_repository().get( + "show_thoughts", False + ): + logger.debug( + f"Displaying structured thinking content ({len(thinking_content)} chars)" ) - + console.print( + Panel( + Markdown(thinking_content), + title="💭 Expert Thinking", + border_style="yellow", + ) + ) + # Use response_text if available, otherwise fall back to joining if response_text: content = response_text else: # Fallback: join list items if structured extraction failed - logger.debug("No structured response text found, joining list items") + logger.debug( + "No structured response text found, joining list items" + ) content = "\n".join(str(item) for item in content) - elif (supports_think_tag or supports_thinking): + elif supports_think_tag or supports_thinking: # Process thinking content using the centralized function content, _ = process_thinking_content( content=content, @@ -994,24 +1065,28 @@ def run_planning_agent( supports_thinking=supports_thinking, panel_title="💭 Expert Thinking", panel_style="yellow", - logger=logger + logger=logger, ) - + # Display the expert guidance in a panel console.print( - Panel(Markdown(content), title="Reasoning Guidance", border_style="blue") + Panel( + Markdown(content), title="Reasoning Guidance", border_style="blue" + ) ) - + # Use the content as expert guidance - expert_guidance = content + "\n\nCONSULT WITH THE EXPERT FREQUENTLY ON THIS TASK" - + expert_guidance = ( + content + "\n\nCONSULT WITH THE EXPERT FREQUENTLY ON THIS TASK" + ) + logger.info("Received expert guidance for planning") except Exception as e: logger.error("Error getting expert guidance for planning: %s", e) expert_guidance = "" - + agent = create_agent(model, tools, checkpointer=memory, agent_type="planner") - + expert_section = EXPERT_PROMPT_SECTION_PLANNING if expert_enabled else "" human_section = HUMAN_PROMPT_SECTION_PLANNING if hil else "" web_research_section = ( @@ -1019,7 +1094,7 @@ def run_planning_agent( if get_config_repository().get("web_research_enabled", False) else "" ) - + # Prepare expert guidance section if expert guidance is available expert_guidance_section = "" if expert_guidance: @@ -1050,7 +1125,9 @@ def run_planning_agent( ) config_values = get_config_repository().get_all() - recursion_limit = get_config_repository().get("recursion_limit", DEFAULT_RECURSION_LIMIT) + recursion_limit = get_config_repository().get( + "recursion_limit", DEFAULT_RECURSION_LIMIT + ) run_config = { "configurable": {"thread_id": thread_id}, "recursion_limit": recursion_limit, @@ -1060,9 +1137,7 @@ def run_planning_agent( try: logger.debug("Planning agent completed successfully") none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry( - agent, planning_prompt, none_or_fallback_handler - ) + _result = run_agent_with_retry(agent, planning_prompt, none_or_fallback_handler) if _result: # Log planning completion log_work_event(f"Completed planning phase for: {base_task}") @@ -1135,7 +1210,7 @@ def run_task_implementation_agent( except RuntimeError as e: logger.error(f"Failed to access key fact repository: {str(e)}") key_facts = "" - + # Get formatted research notes using repository try: repository = get_research_note_repository() @@ -1144,7 +1219,7 @@ def run_task_implementation_agent( except RuntimeError as e: logger.error(f"Failed to access research note repository: {str(e)}") formatted_research_notes = "" - + # Get latest project info try: project_info = get_project_info(".") @@ -1152,24 +1227,26 @@ def run_task_implementation_agent( except Exception as e: logger.warning("Failed to get project info: %s", str(e)) formatted_project_info = "Project info unavailable" - + # Get environment inventory information env_inv = get_env_inv() - + # Get model configuration to check for reasoning_assist_default provider = get_config_repository().get("provider", "") model_name = get_config_repository().get("model", "") logger.debug("Checking for reasoning_assist_default on %s/%s", provider, model_name) - + model_config = {} provider_models = models_params.get(provider, {}) if provider_models and model_name in provider_models: model_config = provider_models[model_name] - + # Check if reasoning assist is explicitly enabled/disabled force_assistance = get_config_repository().get("force_reasoning_assistance", False) - disable_assistance = get_config_repository().get("disable_reasoning_assistance", False) - + disable_assistance = get_config_repository().get( + "disable_reasoning_assistance", False + ) + if force_assistance: reasoning_assist_enabled = True elif disable_assistance: @@ -1177,71 +1254,84 @@ def run_task_implementation_agent( else: # Fall back to model default reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) - + logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) - + # Initialize implementation guidance section implementation_guidance_section = "" - + # If reasoning assist is enabled, make a one-off call to the expert model if reasoning_assist_enabled: try: - logger.info("Reasoning assist enabled for model %s, getting implementation guidance", model_name) - + logger.info( + "Reasoning assist enabled for model %s, getting implementation guidance", + model_name, + ) + # Collect tool descriptions tool_metadata = [] from ra_aid.tools.reflection import get_function_info as get_tool_info - + for tool in tools: try: tool_info = get_tool_info(tool.func) name = tool.func.__name__ description = inspect.getdoc(tool.func) - tool_metadata.append(f"Tool: {name}\\nDescription: {description}\\n") + tool_metadata.append( + f"Tool: {name}\\nDescription: {description}\\n" + ) except Exception as e: logger.warning(f"Error getting tool info for {tool}: {e}") - + # Format tool metadata formatted_tool_metadata = "\\n".join(tool_metadata) - + # Initialize expert model expert_model = initialize_expert_llm(provider, model_name) - + # Format the reasoning assist prompt for implementation reasoning_assist_prompt = REASONING_ASSIST_PROMPT_IMPLEMENTATION.format( current_date=current_date, working_directory=working_directory, task=task, key_facts=key_facts, - key_snippets=format_key_snippets_dict(get_key_snippet_repository().get_snippets_dict()), + key_snippets=format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ), research_notes=formatted_research_notes, related_files="\\n".join(related_files), env_inv=env_inv, tool_metadata=formatted_tool_metadata, ) - + # Show the reasoning assist query in a panel console.print( - Panel(Markdown("Consulting with the reasoning model on the best implementation approach."), title="📝 Thinking about implementation...", border_style="yellow") + Panel( + Markdown( + "Consulting with the reasoning model on the best implementation approach." + ), + title="📝 Thinking about implementation...", + border_style="yellow", + ) ) - + logger.debug("Invoking expert model for implementation reasoning assist") # Make the call to the expert model response = expert_model.invoke(reasoning_assist_prompt) - + # Check if the model supports think tags supports_think_tag = model_config.get("supports_think_tag", False) supports_thinking = model_config.get("supports_thinking", False) - + # Process response content content = None - - if hasattr(response, 'content'): + + if hasattr(response, "content"): content = response.content else: # Fallback if content attribute is missing content = str(response) - + # Process the response content using the centralized function content, extracted_thinking = process_thinking_content( content=content, @@ -1249,24 +1339,28 @@ def run_task_implementation_agent( supports_thinking=supports_thinking, panel_title="💭 Implementation Thinking", panel_style="yellow", - logger=logger + logger=logger, ) - + # Display the implementation guidance in a panel console.print( - Panel(Markdown(content), title="Implementation Guidance", border_style="blue") + Panel( + Markdown(content), + title="Implementation Guidance", + border_style="blue", + ) ) - + # Format the implementation guidance section for the prompt implementation_guidance_section = f""" {content} """ - + logger.info("Received implementation guidance") except Exception as e: logger.error("Error getting implementation guidance: %s", e) implementation_guidance_section = "" - + prompt = IMPLEMENTATION_PROMPT.format( current_date=current_date, working_directory=working_directory, @@ -1276,7 +1370,9 @@ def run_task_implementation_agent( plan=plan, related_files=related_files, key_facts=key_facts, - key_snippets=format_key_snippets_dict(get_key_snippet_repository().get_snippets_dict()), + key_snippets=format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ), research_notes=formatted_research_notes, work_log=get_work_log_repository().format_work_log(), expert_section=EXPERT_PROMPT_SECTION_IMPLEMENTATION if expert_enabled else "", @@ -1296,7 +1392,9 @@ def run_task_implementation_agent( ) config_values = get_config_repository().get_all() - recursion_limit = get_config_repository().get("recursion_limit", DEFAULT_RECURSION_LIMIT) + recursion_limit = get_config_repository().get( + "recursion_limit", DEFAULT_RECURSION_LIMIT + ) run_config = { "configurable": {"thread_id": thread_id}, "recursion_limit": recursion_limit, @@ -1306,9 +1404,7 @@ def run_task_implementation_agent( try: logger.debug("Implementation agent completed successfully") none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry( - agent, prompt, none_or_fallback_handler - ) + _result = run_agent_with_retry(agent, prompt, none_or_fallback_handler) if _result: # Log task implementation completion log_work_event(f"Completed implementation of task: {task}") @@ -1380,27 +1476,37 @@ def _handle_api_error(e, attempt, max_retries, base_delay): # 1. Check if this is a ValueError with 429 code or rate limit phrases if isinstance(e, ValueError): error_str = str(e).lower() - rate_limit_phrases = ["429", "rate limit", "too many requests", "quota exceeded"] - if "code" not in error_str and not any(phrase in error_str for phrase in rate_limit_phrases): + rate_limit_phrases = [ + "429", + "rate limit", + "too many requests", + "quota exceeded", + ] + if "code" not in error_str and not any( + phrase in error_str for phrase in rate_limit_phrases + ): raise e - + # 2. Check for status_code or http_status attribute equal to 429 - if hasattr(e, 'status_code') and e.status_code == 429: + if hasattr(e, "status_code") and e.status_code == 429: pass # This is a rate limit error, continue with retry logic - elif hasattr(e, 'http_status') and e.http_status == 429: + elif hasattr(e, "http_status") and e.http_status == 429: pass # This is a rate limit error, continue with retry logic # 3. Check for rate limit phrases in error message elif isinstance(e, Exception) and not isinstance(e, ValueError): error_str = str(e).lower() - if not any(phrase in error_str for phrase in ["rate limit", "too many requests", "quota exceeded", "429"]) and not ("rate" in error_str and "limit" in error_str): + if not any( + phrase in error_str + for phrase in ["rate limit", "too many requests", "quota exceeded", "429"] + ) and not ("rate" in error_str and "limit" in error_str): # This doesn't look like a rate limit error, but we'll still retry other API errors pass - + # Apply common retry logic for all identified errors if attempt == max_retries - 1: logger.error("Max retries reached, failing: %s", str(e)) raise RuntimeError(f"Max retries ({max_retries}) exceeded. Last error: {e}") - + logger.warning("API error (attempt %d/%d): %s", attempt + 1, max_retries, str(e)) delay = base_delay * (2**attempt) print_error( @@ -1457,55 +1563,78 @@ def _handle_fallback_response( def _run_agent_stream(agent: RAgents, msg_list: list[BaseMessage]): """ Streams agent output while handling completion and interruption. - + For each chunk, it logs the output, calls check_interrupt(), prints agent output, and then checks if is_completed() or should_exit() are true. If so, it resets completion flags and returns. After finishing a stream iteration (i.e. the for-loop over chunks), the function retrieves the agent's state. If the state indicates further steps (i.e. state.next is non-empty), it resumes execution via agent.invoke(None, config); otherwise, it exits the loop. - + This function adheres to the latest LangGraph best practices (as of March 2025) for handling human-in-the-loop interruptions using interrupt_after=["tools"]. """ config = get_config_repository().get_all() + stream_config = config.copy() + + cb = None + if is_anthropic_claude(config): + model_name = config.get("model", "") + full_model_name = model_name + cb = AnthropicCallbackHandler(full_model_name) + + if "callbacks" not in stream_config: + stream_config["callbacks"] = [] + stream_config["callbacks"].append(cb) + while True: - # Process each chunk from the agent stream. - for chunk in agent.stream({"messages": msg_list}, config): + for chunk in agent.stream({"messages": msg_list}, stream_config): logger.debug("Agent output: %s", chunk) check_interrupt() agent_type = get_agent_type(agent) print_agent_output(chunk, agent_type) + if is_completed() or should_exit(): reset_completion_flags() - return True # Exit immediately when finished or signaled to exit. + if cb: + logger.debug(f"AnthropicCallbackHandler:\n{cb}") + return True + logger.debug("Stream iteration ended; checking agent state for continuation.") - + # Prepare state configuration, ensuring 'configurable' is present. state_config = get_config_repository().get_all().copy() if "configurable" not in state_config: - logger.debug("Key 'configurable' not found in config; adding it as an empty dict.") + logger.debug( + "Key 'configurable' not found in config; adding it as an empty dict." + ) state_config["configurable"] = {} logger.debug("Using state_config for agent.get_state(): %s", state_config) - + try: state = agent.get_state(state_config) logger.debug("Agent state retrieved: %s", state) except Exception as e: - logger.error("Error retrieving agent state with state_config %s: %s", state_config, e) + logger.error( + "Error retrieving agent state with state_config %s: %s", state_config, e + ) raise - - # If the state indicates that further steps remain (i.e. state.next is non-empty), - # then resume execution by invoking the agent with no new input. + if state.next: - logger.debug("State indicates continuation (state.next: %s); resuming execution.", state.next) - agent.invoke(None, config) + logger.debug( + "State indicates continuation (state.next: %s); resuming execution.", + state.next, + ) + agent.invoke(None, stream_config) continue else: logger.debug("No continuation indicated in state; exiting stream loop.") break + if cb: + logger.debug(f"AnthropicCallbackHandler:\n{cb}") return True + def run_agent_with_retry( agent: RAgents, prompt: str, @@ -1517,7 +1646,9 @@ def run_agent_with_retry( max_retries = 20 base_delay = 1 test_attempts = 0 - _max_test_retries = get_config_repository().get("max_test_cmd_retries", DEFAULT_MAX_TEST_CMD_RETRIES) + _max_test_retries = get_config_repository().get( + "max_test_cmd_retries", DEFAULT_MAX_TEST_CMD_RETRIES + ) auto_test = get_config_repository().get("auto_test", False) original_prompt = prompt msg_list = [HumanMessage(content=prompt)] diff --git a/ra_aid/callbacks/anthropic_callback_handler.py b/ra_aid/callbacks/anthropic_callback_handler.py new file mode 100644 index 0000000..2bdf737 --- /dev/null +++ b/ra_aid/callbacks/anthropic_callback_handler.py @@ -0,0 +1,270 @@ +"""Custom callback handlers for tracking token usage and costs.""" + +import threading +from contextlib import contextmanager +from contextvars import ContextVar +from typing import Any, Dict, List, Optional + +from langchain_core.callbacks import BaseCallbackHandler + +# Define cost per 1K tokens for various models +ANTHROPIC_MODEL_COSTS = { + # Claude 3.7 Sonnet input + "claude-3-7-sonnet-20250219": 0.003, + "anthropic/claude-3.7-sonnet": 0.003, + "claude-3.7-sonnet": 0.003, + # Claude 3.7 Sonnet output + "claude-3-7-sonnet-20250219-completion": 0.015, + "anthropic/claude-3.7-sonnet-completion": 0.015, + "claude-3.7-sonnet-completion": 0.015, + # Claude 3 Opus input + "claude-3-opus-20240229": 0.015, + "anthropic/claude-3-opus": 0.015, + "claude-3-opus": 0.015, + # Claude 3 Opus output + "claude-3-opus-20240229-completion": 0.075, + "anthropic/claude-3-opus-completion": 0.075, + "claude-3-opus-completion": 0.075, + # Claude 3 Sonnet input + "claude-3-sonnet-20240229": 0.003, + "anthropic/claude-3-sonnet": 0.003, + "claude-3-sonnet": 0.003, + # Claude 3 Sonnet output + "claude-3-sonnet-20240229-completion": 0.015, + "anthropic/claude-3-sonnet-completion": 0.015, + "claude-3-sonnet-completion": 0.015, + # Claude 3 Haiku input + "claude-3-haiku-20240307": 0.00025, + "anthropic/claude-3-haiku": 0.00025, + "claude-3-haiku": 0.00025, + # Claude 3 Haiku output + "claude-3-haiku-20240307-completion": 0.00125, + "anthropic/claude-3-haiku-completion": 0.00125, + "claude-3-haiku-completion": 0.00125, + # Claude 2 input + "claude-2": 0.008, + "claude-2.0": 0.008, + "claude-2.1": 0.008, + # Claude 2 output + "claude-2-completion": 0.024, + "claude-2.0-completion": 0.024, + "claude-2.1-completion": 0.024, + # Claude Instant input + "claude-instant-1": 0.0016, + "claude-instant-1.2": 0.0016, + # Claude Instant output + "claude-instant-1-completion": 0.0055, + "claude-instant-1.2-completion": 0.0055, +} + + +def standardize_model_name(model_name: str, is_completion: bool = False) -> str: + """ + Standardize the model name to a format that can be used for cost calculation. + + Args: + model_name: Model name to standardize. + is_completion: Whether the model is used for completion or not. + + Returns: + Standardized model name. + """ + if not model_name: + model_name = "claude-3-sonnet" + + model_name = model_name.lower() + + # Handle OpenRouter prefixes + if model_name.startswith("anthropic/"): + model_name = model_name[len("anthropic/") :] + + # Add completion suffix if needed + if is_completion and not model_name.endswith("-completion"): + model_name = model_name + "-completion" + + return model_name + + +def get_anthropic_token_cost_for_model( + model_name: str, num_tokens: int, is_completion: bool = False +) -> float: + """ + Get the cost in USD for a given model and number of tokens. + + Args: + model_name: Name of the model + num_tokens: Number of tokens. + is_completion: Whether the model is used for completion or not. + + Returns: + Cost in USD. + """ + model_name = standardize_model_name(model_name, is_completion) + + if model_name not in ANTHROPIC_MODEL_COSTS: + # Default to Claude 3 Sonnet pricing if model not found + model_name = ( + "claude-3-sonnet" if not is_completion else "claude-3-sonnet-completion" + ) + + cost_per_1k = ANTHROPIC_MODEL_COSTS[model_name] + total_cost = cost_per_1k * (num_tokens / 1000) + + return total_cost + + +class AnthropicCallbackHandler(BaseCallbackHandler): + """Callback Handler that tracks Anthropic token usage and costs.""" + + total_tokens: int = 0 + prompt_tokens: int = 0 + completion_tokens: int = 0 + successful_requests: int = 0 + total_cost: float = 0.0 + model_name: str = "claude-3-sonnet" # Default model + + def __init__(self, model_name: Optional[str] = None) -> None: + super().__init__() + self._lock = threading.Lock() + if model_name: + self.model_name = model_name + + # Default costs for Claude 3.7 Sonnet + self.input_cost_per_token = 0.003 / 1000 # $3/M input tokens + self.output_cost_per_token = 0.015 / 1000 # $15/M output tokens + + def __repr__(self) -> str: + return ( + f"Tokens Used: {self.total_tokens}\n" + f"\tPrompt Tokens: {self.prompt_tokens}\n" + f"\tCompletion Tokens: {self.completion_tokens}\n" + f"Successful Requests: {self.successful_requests}\n" + f"Total Cost (USD): ${self.total_cost:.6f}" + ) + + @property + def always_verbose(self) -> bool: + """Whether to call verbose callbacks even if verbose is False.""" + return True + + def on_llm_start( + self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any + ) -> None: + """Record the model name if available.""" + if "name" in serialized: + self.model_name = serialized["name"] + + def on_llm_new_token(self, token: str, **kwargs: Any) -> None: + """Count tokens as they're generated.""" + with self._lock: + self.completion_tokens += 1 + self.total_tokens += 1 + token_cost = get_anthropic_token_cost_for_model( + self.model_name, 1, is_completion=True + ) + self.total_cost += token_cost + + def on_llm_end(self, response: Any, **kwargs: Any) -> None: + """Collect token usage from response.""" + token_usage = {} + + # Try to extract token usage from response + if hasattr(response, "llm_output") and response.llm_output: + llm_output = response.llm_output + if "token_usage" in llm_output: + token_usage = llm_output["token_usage"] + elif "usage" in llm_output: + usage = llm_output["usage"] + + # Handle Anthropic's specific usage format + if "input_tokens" in usage: + token_usage["prompt_tokens"] = usage["input_tokens"] + if "output_tokens" in usage: + token_usage["completion_tokens"] = usage["output_tokens"] + + # Extract model name if available + if "model_name" in llm_output: + self.model_name = llm_output["model_name"] + + # Try to get usage from response.usage + elif hasattr(response, "usage"): + usage = response.usage + if hasattr(usage, "prompt_tokens"): + token_usage["prompt_tokens"] = usage.prompt_tokens + if hasattr(usage, "completion_tokens"): + token_usage["completion_tokens"] = usage.completion_tokens + if hasattr(usage, "total_tokens"): + token_usage["total_tokens"] = usage.total_tokens + + # Extract usage from generations if available + elif hasattr(response, "generations") and response.generations: + for gen in response.generations: + if gen and hasattr(gen[0], "generation_info"): + gen_info = gen[0].generation_info or {} + if "usage" in gen_info: + token_usage = gen_info["usage"] + break + + # Update counts with lock to prevent race conditions + with self._lock: + prompt_tokens = token_usage.get("prompt_tokens", 0) + completion_tokens = token_usage.get("completion_tokens", 0) + + # Only update prompt tokens if we have them + if prompt_tokens > 0: + self.prompt_tokens += prompt_tokens + self.total_tokens += prompt_tokens + prompt_cost = get_anthropic_token_cost_for_model( + self.model_name, prompt_tokens, is_completion=False + ) + self.total_cost += prompt_cost + + # Only update completion tokens if not already counted by on_llm_new_token + if completion_tokens > 0 and completion_tokens > self.completion_tokens: + additional_tokens = completion_tokens - self.completion_tokens + self.completion_tokens = completion_tokens + self.total_tokens += additional_tokens + completion_cost = get_anthropic_token_cost_for_model( + self.model_name, additional_tokens, is_completion=True + ) + self.total_cost += completion_cost + + self.successful_requests += 1 + + def __copy__(self) -> "AnthropicCallbackHandler": + """Return a copy of the callback handler.""" + return self + + def __deepcopy__(self, memo: Any) -> "AnthropicCallbackHandler": + """Return a deep copy of the callback handler.""" + return self + + +# Create a context variable for our custom callback +anthropic_callback_var: ContextVar[Optional[AnthropicCallbackHandler]] = ContextVar( + "anthropic_callback", default=None +) + + +@contextmanager +def get_anthropic_callback( + model_name: Optional[str] = None, +) -> AnthropicCallbackHandler: + """Get the Anthropic callback handler in a context manager. + which conveniently exposes token and cost information. + + Args: + model_name: Optional model name to use for cost calculation. + + Returns: + AnthropicCallbackHandler: The Anthropic callback handler. + + Example: + >>> with get_anthropic_callback("claude-3-sonnet") as cb: + ... # Use the callback handler + ... # cb.total_tokens, cb.total_cost will be available after + """ + cb = AnthropicCallbackHandler(model_name) + anthropic_callback_var.set(cb) + yield cb + anthropic_callback_var.set(None) diff --git a/uv.lock b/uv.lock index e88265d..3cb0473 100644 --- a/uv.lock +++ b/uv.lock @@ -11,7 +11,7 @@ resolution-markers = [ [[package]] name = "aider-chat" -version = "0.74.2" +version = "0.75.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -91,6 +91,7 @@ dependencies = [ { name = "six" }, { name = "smmap" }, { name = "sniffio" }, + { name = "socksio" }, { name = "sounddevice" }, { name = "soundfile" }, { name = "soupsieve" }, @@ -106,23 +107,23 @@ dependencies = [ { name = "yarl" }, { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/f2/7e92fa2d7d9516a23652061291851403cc24a5545db0bf73874011ccdd54/aider_chat-0.74.2.tar.gz", hash = "sha256:47fca536698e9ac3daa148ccc447d5b220a4c6126ab05034225ee51cd8d69014", size = 1180271 } +sdist = { url = "https://files.pythonhosted.org/packages/9d/f8/5a4ccbbf8a8368fd5ccdd1621c1296289360692da822123b7f0b27336fd7/aider_chat-0.75.2.tar.gz", hash = "sha256:d8a66c592efd2fd024f5387f311b03e90e738ff37d2217b8b6d650285ee0e589", size = 1183264 } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/34/8f25f7eda8ff172745e0934719552a1c701339b4851dae44c20684086bf1/aider_chat-0.74.2-py3-none-any.whl", hash = "sha256:47c39a6dbce600ea3af20b97b9ee82568d7622f187a42571c3e1b0918bcdb425", size = 285369 }, + { url = "https://files.pythonhosted.org/packages/1d/1e/2f9f711d54cfd517bfebf8c9eb1ccfda5fd61c759c815f962b16eb6d7f4e/aider_chat-0.75.2-py3-none-any.whl", hash = "sha256:a4f6c61b715671cf8488cc04fe3ee8e4e31b8f831f2c6361fdfa0acadc7cc84e", size = 289217 }, ] [[package]] name = "aiohappyeyeballs" -version = "2.4.6" +version = "2.4.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/07/508f9ebba367fc3370162e53a3cfd12f5652ad79f0e0bfdf9f9847c6f159/aiohappyeyeballs-2.4.6.tar.gz", hash = "sha256:9b05052f9042985d32ecbe4b59a77ae19c006a78f1344d7fdad69d28ded3d0b0", size = 21726 } +sdist = { url = "https://files.pythonhosted.org/packages/de/7c/79a15272e88d2563c9d63599fa59f05778975f35b255bf8f90c8b12b4ada/aiohappyeyeballs-2.4.8.tar.gz", hash = "sha256:19728772cb12263077982d2f55453babd8bec6a052a926cd5c0c42796da8bf62", size = 22337 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/4c/03fb05f56551828ec67ceb3665e5dc51638042d204983a03b0a1541475b6/aiohappyeyeballs-2.4.6-py3-none-any.whl", hash = "sha256:147ec992cf873d74f5062644332c539fcd42956dc69453fe5204195e560517e1", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/52/0e/b187e2bb3eeb2644515109657c4474d65a84e7123de249bf1e8467d04a65/aiohappyeyeballs-2.4.8-py3-none-any.whl", hash = "sha256:6cac4f5dd6e34a9644e69cf9021ef679e4394f54e58a183056d12009e42ea9e3", size = 15005 }, ] [[package]] name = "aiohttp" -version = "3.11.12" +version = "3.11.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -134,88 +135,88 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/4b/952d49c73084fb790cb5c6ead50848c8e96b4980ad806cf4d2ad341eaa03/aiohttp-3.11.12.tar.gz", hash = "sha256:7603ca26d75b1b86160ce1bbe2787a0b706e592af5b2504e12caa88a217767b0", size = 7673175 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/3f/c4a667d184c69667b8f16e0704127efc5f1e60577df429382b4d95fd381e/aiohttp-3.11.13.tar.gz", hash = "sha256:8ce789231404ca8fff7f693cdce398abf6d90fd5dae2b1847477196c243b1fbb", size = 7674284 } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/42/3880e133590820aa7bc6d068eb7d8e0ad9fdce9b4663f92b821d3f6b5601/aiohttp-3.11.12-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa8a8caca81c0a3e765f19c6953416c58e2f4cc1b84829af01dd1c771bb2f91f", size = 708721 }, - { url = "https://files.pythonhosted.org/packages/d8/8c/04869803bed108b25afad75f94c651b287851843caacbec6677d8f2d572b/aiohttp-3.11.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84ede78acde96ca57f6cf8ccb8a13fbaf569f6011b9a52f870c662d4dc8cd854", size = 468596 }, - { url = "https://files.pythonhosted.org/packages/4f/f4/9074011f0d1335b161c953fb32545b6667cf24465e1932b9767874995c7e/aiohttp-3.11.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:584096938a001378484aa4ee54e05dc79c7b9dd933e271c744a97b3b6f644957", size = 455758 }, - { url = "https://files.pythonhosted.org/packages/fd/68/06298c57ef8f534065930b805e6dbd83613f0534447922782fb9920fce28/aiohttp-3.11.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:392432a2dde22b86f70dd4a0e9671a349446c93965f261dbaecfaf28813e5c42", size = 1584797 }, - { url = "https://files.pythonhosted.org/packages/bd/1e/cee6b51fcb3b1c4185a7dc62b3113bc136fae07f39386c88c90b7f79f199/aiohttp-3.11.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88d385b8e7f3a870146bf5ea31786ef7463e99eb59e31db56e2315535d811f55", size = 1632535 }, - { url = "https://files.pythonhosted.org/packages/71/1f/42424462b7a09da362e1711090db9f8d68a37a33f0aab51307335517c599/aiohttp-3.11.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b10a47e5390c4b30a0d58ee12581003be52eedd506862ab7f97da7a66805befb", size = 1668484 }, - { url = "https://files.pythonhosted.org/packages/f6/79/0e25542bbe3c2bfd7a12c7a49c7bce73b09a836f65079e4b77bc2bafc89e/aiohttp-3.11.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b5263dcede17b6b0c41ef0c3ccce847d82a7da98709e75cf7efde3e9e3b5cae", size = 1589708 }, - { url = "https://files.pythonhosted.org/packages/d1/13/93ae26b75e23f7d3a613872e472fae836ca100dc5bde5936ebc93ada8890/aiohttp-3.11.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50c5c7b8aa5443304c55c262c5693b108c35a3b61ef961f1e782dd52a2f559c7", size = 1544752 }, - { url = "https://files.pythonhosted.org/packages/cf/5e/48847fad1b014ef92ef18ea1339a3b58eb81d3bc717b94c3627f5d2a42c5/aiohttp-3.11.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1c031a7572f62f66f1257db37ddab4cb98bfaf9b9434a3b4840bf3560f5e788", size = 1529417 }, - { url = "https://files.pythonhosted.org/packages/ae/56/fbd4ea019303f4877f0e0b8c9de92e9db24338e7545570d3f275f3c74c53/aiohttp-3.11.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7e44eba534381dd2687be50cbd5f2daded21575242ecfdaf86bbeecbc38dae8e", size = 1557808 }, - { url = "https://files.pythonhosted.org/packages/f1/43/112189cf6b3c482ecdd6819b420eaa0c2033426f28d741bb7f19db5dd2bb/aiohttp-3.11.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:145a73850926018ec1681e734cedcf2716d6a8697d90da11284043b745c286d5", size = 1536765 }, - { url = "https://files.pythonhosted.org/packages/30/12/59986547de8306e06c7b30e547ccda02d29636e152366caba2dd8627bfe1/aiohttp-3.11.12-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2c311e2f63e42c1bf86361d11e2c4a59f25d9e7aabdbdf53dc38b885c5435cdb", size = 1607621 }, - { url = "https://files.pythonhosted.org/packages/aa/9b/af3b323b20df3318ed20d701d8242e523d59c842ca93f23134b05c9d5054/aiohttp-3.11.12-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ea756b5a7bac046d202a9a3889b9a92219f885481d78cd318db85b15cc0b7bcf", size = 1628977 }, - { url = "https://files.pythonhosted.org/packages/36/62/adf5a331a7bda475cc326dde393fa2bc5849060b1b37ac3d1bee1953f2cd/aiohttp-3.11.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:526c900397f3bbc2db9cb360ce9c35134c908961cdd0ac25b1ae6ffcaa2507ff", size = 1564455 }, - { url = "https://files.pythonhosted.org/packages/90/c4/4a24291f22f111a854dfdb54dc94d4e0a5229ccbb7bc7f0bed972aa50410/aiohttp-3.11.12-cp310-cp310-win32.whl", hash = "sha256:b8d3bb96c147b39c02d3db086899679f31958c5d81c494ef0fc9ef5bb1359b3d", size = 416768 }, - { url = "https://files.pythonhosted.org/packages/51/69/5221c8006acb7bb10d9e8e2238fb216571bddc2e00a8d95bcfbe2f579c57/aiohttp-3.11.12-cp310-cp310-win_amd64.whl", hash = "sha256:7fe3d65279bfbee8de0fb4f8c17fc4e893eed2dba21b2f680e930cc2b09075c5", size = 442170 }, - { url = "https://files.pythonhosted.org/packages/9c/38/35311e70196b6a63cfa033a7f741f800aa8a93f57442991cbe51da2394e7/aiohttp-3.11.12-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87a2e00bf17da098d90d4145375f1d985a81605267e7f9377ff94e55c5d769eb", size = 708797 }, - { url = "https://files.pythonhosted.org/packages/44/3e/46c656e68cbfc4f3fc7cb5d2ba4da6e91607fe83428208028156688f6201/aiohttp-3.11.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b34508f1cd928ce915ed09682d11307ba4b37d0708d1f28e5774c07a7674cac9", size = 468669 }, - { url = "https://files.pythonhosted.org/packages/a0/d6/2088fb4fd1e3ac2bfb24bc172223babaa7cdbb2784d33c75ec09e66f62f8/aiohttp-3.11.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:936d8a4f0f7081327014742cd51d320296b56aa6d324461a13724ab05f4b2933", size = 455739 }, - { url = "https://files.pythonhosted.org/packages/e7/dc/c443a6954a56f4a58b5efbfdf23cc6f3f0235e3424faf5a0c56264d5c7bb/aiohttp-3.11.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de1378f72def7dfb5dbd73d86c19eda0ea7b0a6873910cc37d57e80f10d64e1", size = 1685858 }, - { url = "https://files.pythonhosted.org/packages/25/67/2d5b3aaade1d5d01c3b109aa76e3aa9630531252cda10aa02fb99b0b11a1/aiohttp-3.11.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9d45dbb3aaec05cf01525ee1a7ac72de46a8c425cb75c003acd29f76b1ffe94", size = 1743829 }, - { url = "https://files.pythonhosted.org/packages/90/9b/9728fe9a3e1b8521198455d027b0b4035522be18f504b24c5d38d59e7278/aiohttp-3.11.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:930ffa1925393381e1e0a9b82137fa7b34c92a019b521cf9f41263976666a0d6", size = 1785587 }, - { url = "https://files.pythonhosted.org/packages/ce/cf/28fbb43d4ebc1b4458374a3c7b6db3b556a90e358e9bbcfe6d9339c1e2b6/aiohttp-3.11.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8340def6737118f5429a5df4e88f440746b791f8f1c4ce4ad8a595f42c980bd5", size = 1675319 }, - { url = "https://files.pythonhosted.org/packages/e5/d2/006c459c11218cabaa7bca401f965c9cc828efbdea7e1615d4644eaf23f7/aiohttp-3.11.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4016e383f91f2814e48ed61e6bda7d24c4d7f2402c75dd28f7e1027ae44ea204", size = 1619982 }, - { url = "https://files.pythonhosted.org/packages/9d/83/ca425891ebd37bee5d837110f7fddc4d808a7c6c126a7d1b5c3ad72fc6ba/aiohttp-3.11.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c0600bcc1adfaaac321422d615939ef300df81e165f6522ad096b73439c0f58", size = 1654176 }, - { url = "https://files.pythonhosted.org/packages/25/df/047b1ce88514a1b4915d252513640184b63624e7914e41d846668b8edbda/aiohttp-3.11.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0450ada317a65383b7cce9576096150fdb97396dcfe559109b403c7242faffef", size = 1660198 }, - { url = "https://files.pythonhosted.org/packages/d3/cc/6ecb8e343f0902528620b9dbd567028a936d5489bebd7dbb0dd0914f4fdb/aiohttp-3.11.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:850ff6155371fd802a280f8d369d4e15d69434651b844bde566ce97ee2277420", size = 1650186 }, - { url = "https://files.pythonhosted.org/packages/f8/f8/453df6dd69256ca8c06c53fc8803c9056e2b0b16509b070f9a3b4bdefd6c/aiohttp-3.11.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8fd12d0f989c6099e7b0f30dc6e0d1e05499f3337461f0b2b0dadea6c64b89df", size = 1733063 }, - { url = "https://files.pythonhosted.org/packages/55/f8/540160787ff3000391de0e5d0d1d33be4c7972f933c21991e2ea105b2d5e/aiohttp-3.11.12-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:76719dd521c20a58a6c256d058547b3a9595d1d885b830013366e27011ffe804", size = 1755306 }, - { url = "https://files.pythonhosted.org/packages/30/7d/49f3bfdfefd741576157f8f91caa9ff61a6f3d620ca6339268327518221b/aiohttp-3.11.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97fe431f2ed646a3b56142fc81d238abcbaff08548d6912acb0b19a0cadc146b", size = 1692909 }, - { url = "https://files.pythonhosted.org/packages/40/9c/8ce00afd6f6112ce9a2309dc490fea376ae824708b94b7b5ea9cba979d1d/aiohttp-3.11.12-cp311-cp311-win32.whl", hash = "sha256:e10c440d142fa8b32cfdb194caf60ceeceb3e49807072e0dc3a8887ea80e8c16", size = 416584 }, - { url = "https://files.pythonhosted.org/packages/35/97/4d3c5f562f15830de472eb10a7a222655d750839943e0e6d915ef7e26114/aiohttp-3.11.12-cp311-cp311-win_amd64.whl", hash = "sha256:246067ba0cf5560cf42e775069c5d80a8989d14a7ded21af529a4e10e3e0f0e6", size = 442674 }, - { url = "https://files.pythonhosted.org/packages/4d/d0/94346961acb476569fca9a644cc6f9a02f97ef75961a6b8d2b35279b8d1f/aiohttp-3.11.12-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e392804a38353900c3fd8b7cacbea5132888f7129f8e241915e90b85f00e3250", size = 704837 }, - { url = "https://files.pythonhosted.org/packages/a9/af/05c503f1cc8f97621f199ef4b8db65fb88b8bc74a26ab2adb74789507ad3/aiohttp-3.11.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8fa1510b96c08aaad49303ab11f8803787c99222288f310a62f493faf883ede1", size = 464218 }, - { url = "https://files.pythonhosted.org/packages/f2/48/b9949eb645b9bd699153a2ec48751b985e352ab3fed9d98c8115de305508/aiohttp-3.11.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc065a4285307607df3f3686363e7f8bdd0d8ab35f12226362a847731516e42c", size = 456166 }, - { url = "https://files.pythonhosted.org/packages/14/fb/980981807baecb6f54bdd38beb1bd271d9a3a786e19a978871584d026dcf/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddb31f8474695cd61fc9455c644fc1606c164b93bff2490390d90464b4655df", size = 1682528 }, - { url = "https://files.pythonhosted.org/packages/90/cb/77b1445e0a716914e6197b0698b7a3640590da6c692437920c586764d05b/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dec0000d2d8621d8015c293e24589d46fa218637d820894cb7356c77eca3259", size = 1737154 }, - { url = "https://files.pythonhosted.org/packages/ff/24/d6fb1f4cede9ccbe98e4def6f3ed1e1efcb658871bbf29f4863ec646bf38/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3552fe98e90fdf5918c04769f338a87fa4f00f3b28830ea9b78b1bdc6140e0d", size = 1793435 }, - { url = "https://files.pythonhosted.org/packages/17/e2/9f744cee0861af673dc271a3351f59ebd5415928e20080ab85be25641471/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfe7f984f28a8ae94ff3a7953cd9678550dbd2a1f9bda5dd9c5ae627744c78e", size = 1692010 }, - { url = "https://files.pythonhosted.org/packages/90/c4/4a1235c1df544223eb57ba553ce03bc706bdd065e53918767f7fa1ff99e0/aiohttp-3.11.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a481a574af914b6e84624412666cbfbe531a05667ca197804ecc19c97b8ab1b0", size = 1619481 }, - { url = "https://files.pythonhosted.org/packages/60/70/cf12d402a94a33abda86dd136eb749b14c8eb9fec1e16adc310e25b20033/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1987770fb4887560363b0e1a9b75aa303e447433c41284d3af2840a2f226d6e0", size = 1641578 }, - { url = "https://files.pythonhosted.org/packages/1b/25/7211973fda1f5e833fcfd98ccb7f9ce4fbfc0074e3e70c0157a751d00db8/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a4ac6a0f0f6402854adca4e3259a623f5c82ec3f0c049374133bcb243132baf9", size = 1684463 }, - { url = "https://files.pythonhosted.org/packages/93/60/b5905b4d0693f6018b26afa9f2221fefc0dcbd3773fe2dff1a20fb5727f1/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c96a43822f1f9f69cc5c3706af33239489a6294be486a0447fb71380070d4d5f", size = 1646691 }, - { url = "https://files.pythonhosted.org/packages/b4/fc/ba1b14d6fdcd38df0b7c04640794b3683e949ea10937c8a58c14d697e93f/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a5e69046f83c0d3cb8f0d5bd9b8838271b1bc898e01562a04398e160953e8eb9", size = 1702269 }, - { url = "https://files.pythonhosted.org/packages/5e/39/18c13c6f658b2ba9cc1e0c6fb2d02f98fd653ad2addcdf938193d51a9c53/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:68d54234c8d76d8ef74744f9f9fc6324f1508129e23da8883771cdbb5818cbef", size = 1734782 }, - { url = "https://files.pythonhosted.org/packages/9f/d2/ccc190023020e342419b265861877cd8ffb75bec37b7ddd8521dd2c6deb8/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9fd9dcf9c91affe71654ef77426f5cf8489305e1c66ed4816f5a21874b094b9", size = 1694740 }, - { url = "https://files.pythonhosted.org/packages/3f/54/186805bcada64ea90ea909311ffedcd74369bfc6e880d39d2473314daa36/aiohttp-3.11.12-cp312-cp312-win32.whl", hash = "sha256:0ed49efcd0dc1611378beadbd97beb5d9ca8fe48579fc04a6ed0844072261b6a", size = 411530 }, - { url = "https://files.pythonhosted.org/packages/3d/63/5eca549d34d141bcd9de50d4e59b913f3641559460c739d5e215693cb54a/aiohttp-3.11.12-cp312-cp312-win_amd64.whl", hash = "sha256:54775858c7f2f214476773ce785a19ee81d1294a6bedc5cc17225355aab74802", size = 437860 }, - { url = "https://files.pythonhosted.org/packages/c3/9b/cea185d4b543ae08ee478373e16653722c19fcda10d2d0646f300ce10791/aiohttp-3.11.12-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:413ad794dccb19453e2b97c2375f2ca3cdf34dc50d18cc2693bd5aed7d16f4b9", size = 698148 }, - { url = "https://files.pythonhosted.org/packages/91/5c/80d47fe7749fde584d1404a68ade29bcd7e58db8fa11fa38e8d90d77e447/aiohttp-3.11.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a93d28ed4b4b39e6f46fd240896c29b686b75e39cc6992692e3922ff6982b4c", size = 460831 }, - { url = "https://files.pythonhosted.org/packages/8e/f9/de568f8a8ca6b061d157c50272620c53168d6e3eeddae78dbb0f7db981eb/aiohttp-3.11.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d589264dbba3b16e8951b6f145d1e6b883094075283dafcab4cdd564a9e353a0", size = 453122 }, - { url = "https://files.pythonhosted.org/packages/8b/fd/b775970a047543bbc1d0f66725ba72acef788028fce215dc959fd15a8200/aiohttp-3.11.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5148ca8955affdfeb864aca158ecae11030e952b25b3ae15d4e2b5ba299bad2", size = 1665336 }, - { url = "https://files.pythonhosted.org/packages/82/9b/aff01d4f9716245a1b2965f02044e4474fadd2bcfe63cf249ca788541886/aiohttp-3.11.12-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:525410e0790aab036492eeea913858989c4cb070ff373ec3bc322d700bdf47c1", size = 1718111 }, - { url = "https://files.pythonhosted.org/packages/e0/a9/166fd2d8b2cc64f08104aa614fad30eee506b563154081bf88ce729bc665/aiohttp-3.11.12-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bd8695be2c80b665ae3f05cb584093a1e59c35ecb7d794d1edd96e8cc9201d7", size = 1775293 }, - { url = "https://files.pythonhosted.org/packages/13/c5/0d3c89bd9e36288f10dc246f42518ce8e1c333f27636ac78df091c86bb4a/aiohttp-3.11.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0203433121484b32646a5f5ea93ae86f3d9559d7243f07e8c0eab5ff8e3f70e", size = 1677338 }, - { url = "https://files.pythonhosted.org/packages/72/b2/017db2833ef537be284f64ead78725984db8a39276c1a9a07c5c7526e238/aiohttp-3.11.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40cd36749a1035c34ba8d8aaf221b91ca3d111532e5ccb5fa8c3703ab1b967ed", size = 1603365 }, - { url = "https://files.pythonhosted.org/packages/fc/72/b66c96a106ec7e791e29988c222141dd1219d7793ffb01e72245399e08d2/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7442662afebbf7b4c6d28cb7aab9e9ce3a5df055fc4116cc7228192ad6cb484", size = 1618464 }, - { url = "https://files.pythonhosted.org/packages/3f/50/e68a40f267b46a603bab569d48d57f23508801614e05b3369898c5b2910a/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8a2fb742ef378284a50766e985804bd6adb5adb5aa781100b09befdbfa757b65", size = 1657827 }, - { url = "https://files.pythonhosted.org/packages/c5/1d/aafbcdb1773d0ba7c20793ebeedfaba1f3f7462f6fc251f24983ed738aa7/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cee3b117a8d13ab98b38d5b6bdcd040cfb4181068d05ce0c474ec9db5f3c5bb", size = 1616700 }, - { url = "https://files.pythonhosted.org/packages/b0/5e/6cd9724a2932f36e2a6b742436a36d64784322cfb3406ca773f903bb9a70/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f6a19bcab7fbd8f8649d6595624856635159a6527861b9cdc3447af288a00c00", size = 1685643 }, - { url = "https://files.pythonhosted.org/packages/8b/38/ea6c91d5c767fd45a18151675a07c710ca018b30aa876a9f35b32fa59761/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e4cecdb52aaa9994fbed6b81d4568427b6002f0a91c322697a4bfcc2b2363f5a", size = 1715487 }, - { url = "https://files.pythonhosted.org/packages/8e/24/e9edbcb7d1d93c02e055490348df6f955d675e85a028c33babdcaeda0853/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:30f546358dfa0953db92ba620101fefc81574f87b2346556b90b5f3ef16e55ce", size = 1672948 }, - { url = "https://files.pythonhosted.org/packages/25/be/0b1fb737268e003198f25c3a68c2135e76e4754bf399a879b27bd508a003/aiohttp-3.11.12-cp313-cp313-win32.whl", hash = "sha256:ce1bb21fc7d753b5f8a5d5a4bae99566386b15e716ebdb410154c16c91494d7f", size = 410396 }, - { url = "https://files.pythonhosted.org/packages/68/fd/677def96a75057b0a26446b62f8fbb084435b20a7d270c99539c26573bfd/aiohttp-3.11.12-cp313-cp313-win_amd64.whl", hash = "sha256:f7914ab70d2ee8ab91c13e5402122edbc77821c66d2758abb53aabe87f013287", size = 436234 }, - { url = "https://files.pythonhosted.org/packages/a7/bd/358c7032c43d4875dcbedc9113b087ef8bc619bee034f9423335698631e3/aiohttp-3.11.12-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c3623053b85b4296cd3925eeb725e386644fd5bc67250b3bb08b0f144803e7b", size = 709588 }, - { url = "https://files.pythonhosted.org/packages/9f/87/9e4700a56722c139b6ed4ad9be926183545a1b55e82babd9b082be3ef4c5/aiohttp-3.11.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67453e603cea8e85ed566b2700efa1f6916aefbc0c9fcb2e86aaffc08ec38e78", size = 469076 }, - { url = "https://files.pythonhosted.org/packages/c0/fa/585b66076795911800f8f16f0f93ea8fb9bfa5d8fd757bbf78f32d17c2d9/aiohttp-3.11.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6130459189e61baac5a88c10019b21e1f0c6d00ebc770e9ce269475650ff7f73", size = 456148 }, - { url = "https://files.pythonhosted.org/packages/ba/6b/a1fe710860b10d83799af8c63cf2ffb63eac4edaa42d76e9540679545951/aiohttp-3.11.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9060addfa4ff753b09392efe41e6af06ea5dd257829199747b9f15bfad819460", size = 1587566 }, - { url = "https://files.pythonhosted.org/packages/31/78/ab78f36b44c7239c953afd9bb331edf2b3977925de2ce98545d62e415565/aiohttp-3.11.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34245498eeb9ae54c687a07ad7f160053911b5745e186afe2d0c0f2898a1ab8a", size = 1636411 }, - { url = "https://files.pythonhosted.org/packages/e1/5c/b316b559dde4ae983e725132a2fa2518532ad56ca4698d4b71f42af48722/aiohttp-3.11.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dc0fba9a74b471c45ca1a3cb6e6913ebfae416678d90529d188886278e7f3f6", size = 1672484 }, - { url = "https://files.pythonhosted.org/packages/90/08/8c409ab4040276a8c9944d5e444121a2f34151872440b3c69f31c35edf18/aiohttp-3.11.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a478aa11b328983c4444dacb947d4513cb371cd323f3845e53caeda6be5589d5", size = 1589689 }, - { url = "https://files.pythonhosted.org/packages/e0/25/53b4ceffaac5dcaf4772be41f4f06e7201be5407aa743758e1a37f7d1b63/aiohttp-3.11.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c160a04283c8c6f55b5bf6d4cad59bb9c5b9c9cd08903841b25f1f7109ef1259", size = 1544225 }, - { url = "https://files.pythonhosted.org/packages/4a/40/769d221f4067a05974b3352ffa228041bcda72c487689ab4030791691861/aiohttp-3.11.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:edb69b9589324bdc40961cdf0657815df674f1743a8d5ad9ab56a99e4833cfdd", size = 1530391 }, - { url = "https://files.pythonhosted.org/packages/14/48/22527fadfdfca85fb585870ffd98aece982606775fd2f4ee80270f5c85a0/aiohttp-3.11.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ee84c2a22a809c4f868153b178fe59e71423e1f3d6a8cd416134bb231fbf6d3", size = 1559005 }, - { url = "https://files.pythonhosted.org/packages/fd/0e/72144954bae5d80a8857dca18b8ed8e2ef76acf557465545ad5b5b9bfb58/aiohttp-3.11.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bf4480a5438f80e0f1539e15a7eb8b5f97a26fe087e9828e2c0ec2be119a9f72", size = 1536244 }, - { url = "https://files.pythonhosted.org/packages/60/db/a2cfb5565f5e5870757e2d3099f8e24640e746ff2ba9ea899b35b6acad3f/aiohttp-3.11.12-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e6b2732ef3bafc759f653a98881b5b9cdef0716d98f013d376ee8dfd7285abf1", size = 1607092 }, - { url = "https://files.pythonhosted.org/packages/b0/31/87e869650c5532876e83c7c7d9d3f5505c5a738abe991f3ac2264070ee81/aiohttp-3.11.12-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f752e80606b132140883bb262a457c475d219d7163d996dc9072434ffb0784c4", size = 1629268 }, - { url = "https://files.pythonhosted.org/packages/d2/73/25fb4d2d259caf4cf23035204315665976a66292a1055d0937c62273675a/aiohttp-3.11.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab3247d58b393bda5b1c8f31c9edece7162fc13265334217785518dd770792b8", size = 1567511 }, - { url = "https://files.pythonhosted.org/packages/a3/59/ef91da9971e187033476945cd18bebc3974930bde81cdf66099b318df7a6/aiohttp-3.11.12-cp39-cp39-win32.whl", hash = "sha256:0d5176f310a7fe6f65608213cc74f4228e4f4ce9fd10bcb2bb6da8fc66991462", size = 417082 }, - { url = "https://files.pythonhosted.org/packages/e0/fa/6cfc042c0f59d1fa6eaeeb678b9f13b2c0bf1d7803dae81b93ca55ac6288/aiohttp-3.11.12-cp39-cp39-win_amd64.whl", hash = "sha256:74bd573dde27e58c760d9ca8615c41a57e719bff315c9adb6f2a4281a28e8798", size = 442385 }, + { url = "https://files.pythonhosted.org/packages/f2/49/18bde4fbe1f98a12fb548741e65b27c5f0991c1af4ad15c86b537a4ce94a/aiohttp-3.11.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4fe27dbbeec445e6e1291e61d61eb212ee9fed6e47998b27de71d70d3e8777d", size = 708941 }, + { url = "https://files.pythonhosted.org/packages/99/24/417e5ab7074f5c97c9a794b6acdc59f47f2231d43e4d5cec06150035e61e/aiohttp-3.11.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e64ca2dbea28807f8484c13f684a2f761e69ba2640ec49dacd342763cc265ef", size = 468823 }, + { url = "https://files.pythonhosted.org/packages/76/93/159d3a2561bc6d64d32f779d08b17570b1c5fe55b985da7e2df9b3a4ff8f/aiohttp-3.11.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9840be675de208d1f68f84d578eaa4d1a36eee70b16ae31ab933520c49ba1325", size = 455984 }, + { url = "https://files.pythonhosted.org/packages/18/bc/ed0dce45da90d4618ae14e677abbd704aec02e0f54820ea3815c156f0759/aiohttp-3.11.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28a772757c9067e2aee8a6b2b425d0efaa628c264d6416d283694c3d86da7689", size = 1585022 }, + { url = "https://files.pythonhosted.org/packages/75/10/c1e6d59030fcf04ccc253193607b5b7ced0caffd840353e109c51134e5e9/aiohttp-3.11.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b88aca5adbf4625e11118df45acac29616b425833c3be7a05ef63a6a4017bfdb", size = 1632761 }, + { url = "https://files.pythonhosted.org/packages/2d/8e/da1a20fbd2c961f824dc8efeb8d31c32ed4af761c87de83032ad4c4f5237/aiohttp-3.11.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce10ddfbe26ed5856d6902162f71b8fe08545380570a885b4ab56aecfdcb07f4", size = 1668720 }, + { url = "https://files.pythonhosted.org/packages/fa/9e/d0bbdc82236c3fe43b28b3338a13ef9b697b0f7a875b33b950b975cab1f6/aiohttp-3.11.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa48dac27f41b36735c807d1ab093a8386701bbf00eb6b89a0f69d9fa26b3671", size = 1589941 }, + { url = "https://files.pythonhosted.org/packages/ed/14/248ed0385baeee854e495ca7f33b48bb151d1b226ddbf1585bdeb2301fbf/aiohttp-3.11.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89ce611b1eac93ce2ade68f1470889e0173d606de20c85a012bfa24be96cf867", size = 1544978 }, + { url = "https://files.pythonhosted.org/packages/20/b0/b2ad9d24fe85db8330034ac45dde67799af40ca2363c0c9b30126e204ef3/aiohttp-3.11.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78e4dd9c34ec7b8b121854eb5342bac8b02aa03075ae8618b6210a06bbb8a115", size = 1529641 }, + { url = "https://files.pythonhosted.org/packages/11/c6/03bdcb73a67a380b9593d52613ea88edd21ddc4ff5aaf06d4f807dfa2220/aiohttp-3.11.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:66047eacbc73e6fe2462b77ce39fc170ab51235caf331e735eae91c95e6a11e4", size = 1558027 }, + { url = "https://files.pythonhosted.org/packages/0d/ae/e45491c8ca4d1e30ff031fb25b44842e16c326f8467026c3eb2a9c167608/aiohttp-3.11.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ad8f1c19fe277eeb8bc45741c6d60ddd11d705c12a4d8ee17546acff98e0802", size = 1536991 }, + { url = "https://files.pythonhosted.org/packages/19/89/10eb37351dd2b52928a54768a70a58171e43d7914685fe3feec8f681d905/aiohttp-3.11.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64815c6f02e8506b10113ddbc6b196f58dbef135751cc7c32136df27b736db09", size = 1607848 }, + { url = "https://files.pythonhosted.org/packages/a4/fd/492dec170df6ea57bef4bcd26374befdc170b10ba9ac7f51a0214943c20a/aiohttp-3.11.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:967b93f21b426f23ca37329230d5bd122f25516ae2f24a9cea95a30023ff8283", size = 1629208 }, + { url = "https://files.pythonhosted.org/packages/70/46/ef8a02cb171d4779ca1632bc8ac0c5bb89729b091e2a3f4b895d688146b5/aiohttp-3.11.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf1f31f83d16ec344136359001c5e871915c6ab685a3d8dee38e2961b4c81730", size = 1564684 }, + { url = "https://files.pythonhosted.org/packages/8a/03/b1b552d1112b72da94bd1f9f5efb8adbcbbafaa8d495fc0924cd80493f17/aiohttp-3.11.13-cp310-cp310-win32.whl", hash = "sha256:00c8ac69e259c60976aa2edae3f13d9991cf079aaa4d3cd5a49168ae3748dee3", size = 416982 }, + { url = "https://files.pythonhosted.org/packages/b0/2d/b6be8e7905ceba64121268ce28208bafe508a742c1467bf636a41d152284/aiohttp-3.11.13-cp310-cp310-win_amd64.whl", hash = "sha256:90d571c98d19a8b6e793b34aa4df4cee1e8fe2862d65cc49185a3a3d0a1a3996", size = 442389 }, + { url = "https://files.pythonhosted.org/packages/3b/93/8e012ae31ff1bda5d43565d6f9e0bad325ba6f3f2d78f298bd39645be8a3/aiohttp-3.11.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b35aab22419ba45f8fc290d0010898de7a6ad131e468ffa3922b1b0b24e9d2e", size = 709013 }, + { url = "https://files.pythonhosted.org/packages/d8/be/fc7c436678ffe547d038319add8e44fd5e33090158752e5c480aed51a8d0/aiohttp-3.11.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81cba651db8795f688c589dd11a4fbb834f2e59bbf9bb50908be36e416dc760", size = 468896 }, + { url = "https://files.pythonhosted.org/packages/d9/1c/56906111ac9d4dab4baab43c89d35d5de1dbb38085150257895005b08bef/aiohttp-3.11.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f55d0f242c2d1fcdf802c8fabcff25a9d85550a4cf3a9cf5f2a6b5742c992839", size = 455968 }, + { url = "https://files.pythonhosted.org/packages/ba/16/229d36ed27c2bb350320364efb56f906af194616cc15fc5d87f3ef21dbef/aiohttp-3.11.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4bea08a6aad9195ac9b1be6b0c7e8a702a9cec57ce6b713698b4a5afa9c2e33", size = 1686082 }, + { url = "https://files.pythonhosted.org/packages/3a/44/78fd174509c56028672e5dfef886569cfa1fced0c5fd5c4480426db19ac9/aiohttp-3.11.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6070bcf2173a7146bb9e4735b3c62b2accba459a6eae44deea0eb23e0035a23", size = 1744056 }, + { url = "https://files.pythonhosted.org/packages/a3/11/325145c6dce8124b5caadbf763e908f2779c14bb0bc5868744d1e5cb9cb7/aiohttp-3.11.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:718d5deb678bc4b9d575bfe83a59270861417da071ab44542d0fcb6faa686636", size = 1785810 }, + { url = "https://files.pythonhosted.org/packages/95/de/faba18a0af09969e10eb89fdbd4cb968bea95e75449a7fa944d4de7d1d2f/aiohttp-3.11.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f6b2c5b4a4d22b8fb2c92ac98e0747f5f195e8e9448bfb7404cd77e7bfa243f", size = 1675540 }, + { url = "https://files.pythonhosted.org/packages/ea/53/0437c46e960b79ae3b1ff74c1ec12f04bf4f425bd349c8807acb38aae3d7/aiohttp-3.11.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747ec46290107a490d21fe1ff4183bef8022b848cf9516970cb31de6d9460088", size = 1620210 }, + { url = "https://files.pythonhosted.org/packages/04/2f/31769ed8e29cc22baaa4005bd2749a7fd0f61ad0f86024d38dff8e394cf6/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:01816f07c9cc9d80f858615b1365f8319d6a5fd079cd668cc58e15aafbc76a54", size = 1654399 }, + { url = "https://files.pythonhosted.org/packages/b0/24/acb24571815b9a86a8261577c920fd84f819178c02a75b05b1a0d7ab83fb/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a08ad95fcbd595803e0c4280671d808eb170a64ca3f2980dd38e7a72ed8d1fea", size = 1660424 }, + { url = "https://files.pythonhosted.org/packages/91/45/30ca0c3ba5bbf7592eee7489eae30437736f7ff912eaa04cfdcf74edca8c/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c97be90d70f7db3aa041d720bfb95f4869d6063fcdf2bb8333764d97e319b7d0", size = 1650415 }, + { url = "https://files.pythonhosted.org/packages/86/8d/4d887df5e732cc70349243c2c9784911979e7bd71c06f9e7717b8a896f75/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ab915a57c65f7a29353c8014ac4be685c8e4a19e792a79fe133a8e101111438e", size = 1733292 }, + { url = "https://files.pythonhosted.org/packages/40/c9/bd950dac0a4c84d44d8da8d6e0f9c9511d45e02cf908a4e1fca591f46a25/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:35cda4e07f5e058a723436c4d2b7ba2124ab4e0aa49e6325aed5896507a8a42e", size = 1755536 }, + { url = "https://files.pythonhosted.org/packages/32/04/aafeda6b4ed3693a44bb89eae002ebaa74f88b2265a7e68f8a31c33330f5/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:af55314407714fe77a68a9ccaab90fdb5deb57342585fd4a3a8102b6d4370080", size = 1693126 }, + { url = "https://files.pythonhosted.org/packages/a1/4f/67729187e884b0f002a0317d2cc7962a5a0416cadc95ea88ba92477290d9/aiohttp-3.11.13-cp311-cp311-win32.whl", hash = "sha256:42d689a5c0a0c357018993e471893e939f555e302313d5c61dfc566c2cad6185", size = 416800 }, + { url = "https://files.pythonhosted.org/packages/29/23/d98d491ca073ee92cc6a741be97b6b097fb06dacc5f95c0c9350787db549/aiohttp-3.11.13-cp311-cp311-win_amd64.whl", hash = "sha256:b73a2b139782a07658fbf170fe4bcdf70fc597fae5ffe75e5b67674c27434a9f", size = 442891 }, + { url = "https://files.pythonhosted.org/packages/9a/a9/6657664a55f78db8767e396cc9723782ed3311eb57704b0a5dacfa731916/aiohttp-3.11.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eabb269dc3852537d57589b36d7f7362e57d1ece308842ef44d9830d2dc3c90", size = 705054 }, + { url = "https://files.pythonhosted.org/packages/3b/06/f7df1fe062d16422f70af5065b76264f40b382605cf7477fa70553a9c9c1/aiohttp-3.11.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b77ee42addbb1c36d35aca55e8cc6d0958f8419e458bb70888d8c69a4ca833d", size = 464440 }, + { url = "https://files.pythonhosted.org/packages/22/3a/8773ea866735754004d9f79e501fe988bdd56cfac7fdecbc8de17fc093eb/aiohttp-3.11.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55789e93c5ed71832e7fac868167276beadf9877b85697020c46e9a75471f55f", size = 456394 }, + { url = "https://files.pythonhosted.org/packages/7f/61/8e2f2af2327e8e475a2b0890f15ef0bbfd117e321cce1e1ed210df81bbac/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c929f9a7249a11e4aa5c157091cfad7f49cc6b13f4eecf9b747104befd9f56f2", size = 1682752 }, + { url = "https://files.pythonhosted.org/packages/24/ed/84fce816bc8da39aa3f6c1196fe26e47065fea882b1a67a808282029c079/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d33851d85537bbf0f6291ddc97926a754c8f041af759e0aa0230fe939168852b", size = 1737375 }, + { url = "https://files.pythonhosted.org/packages/d9/de/35a5ba9e3d21ebfda1ebbe66f6cc5cbb4d3ff9bd6a03e5e8a788954f8f27/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9229d8613bd8401182868fe95688f7581673e1c18ff78855671a4b8284f47bcb", size = 1793660 }, + { url = "https://files.pythonhosted.org/packages/ff/fe/0f650a8c7c72c8a07edf8ab164786f936668acd71786dd5885fc4b1ca563/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669dd33f028e54fe4c96576f406ebb242ba534dd3a981ce009961bf49960f117", size = 1692233 }, + { url = "https://files.pythonhosted.org/packages/a8/20/185378b3483f968c6303aafe1e33b0da0d902db40731b2b2b2680a631131/aiohttp-3.11.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1b20a1ace54af7db1f95af85da530fe97407d9063b7aaf9ce6a32f44730778", size = 1619708 }, + { url = "https://files.pythonhosted.org/packages/a4/f9/d9c181750980b17e1e13e522d7e82a8d08d3d28a2249f99207ef5d8d738f/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5724cc77f4e648362ebbb49bdecb9e2b86d9b172c68a295263fa072e679ee69d", size = 1641802 }, + { url = "https://files.pythonhosted.org/packages/50/c7/1cb46b72b1788710343b6e59eaab9642bd2422f2d87ede18b1996e0aed8f/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:aa36c35e94ecdb478246dd60db12aba57cfcd0abcad43c927a8876f25734d496", size = 1684678 }, + { url = "https://files.pythonhosted.org/packages/71/87/89b979391de840c5d7c34e78e1148cc731b8aafa84b6a51d02f44b4c66e2/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b5b37c863ad5b0892cc7a4ceb1e435e5e6acd3f2f8d3e11fa56f08d3c67b820", size = 1646921 }, + { url = "https://files.pythonhosted.org/packages/a7/db/a463700ac85b72f8cf68093e988538faaf4e865e3150aa165cf80ee29d6e/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e06cf4852ce8c4442a59bae5a3ea01162b8fcb49ab438d8548b8dc79375dad8a", size = 1702493 }, + { url = "https://files.pythonhosted.org/packages/b8/32/1084e65da3adfb08c7e1b3e94f3e4ded8bd707dee265a412bc377b7cd000/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5194143927e494616e335d074e77a5dac7cd353a04755330c9adc984ac5a628e", size = 1735004 }, + { url = "https://files.pythonhosted.org/packages/a0/bb/a634cbdd97ce5d05c2054a9a35bfc32792d7e4f69d600ad7e820571d095b/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afcb6b275c2d2ba5d8418bf30a9654fa978b4f819c2e8db6311b3525c86fe637", size = 1694964 }, + { url = "https://files.pythonhosted.org/packages/fd/cf/7d29db4e5c28ec316e5d2ac9ac9df0e2e278e9ea910e5c4205b9b64c2c42/aiohttp-3.11.13-cp312-cp312-win32.whl", hash = "sha256:7104d5b3943c6351d1ad7027d90bdd0ea002903e9f610735ac99df3b81f102ee", size = 411746 }, + { url = "https://files.pythonhosted.org/packages/65/a9/13e69ad4fd62104ebd94617f9f2be58231b50bb1e6bac114f024303ac23b/aiohttp-3.11.13-cp312-cp312-win_amd64.whl", hash = "sha256:47dc018b1b220c48089b5b9382fbab94db35bef2fa192995be22cbad3c5730c8", size = 438078 }, + { url = "https://files.pythonhosted.org/packages/87/dc/7d58d33cec693f1ddf407d4ab975445f5cb507af95600f137b81683a18d8/aiohttp-3.11.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9862d077b9ffa015dbe3ce6c081bdf35135948cb89116e26667dd183550833d1", size = 698372 }, + { url = "https://files.pythonhosted.org/packages/84/e7/5d88514c9e24fbc8dd6117350a8ec4a9314f4adae6e89fe32e3e639b0c37/aiohttp-3.11.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbfef0666ae9e07abfa2c54c212ac18a1f63e13e0760a769f70b5717742f3ece", size = 461057 }, + { url = "https://files.pythonhosted.org/packages/96/1a/8143c48a929fa00c6324f85660cb0f47a55ed9385f0c1b72d4b8043acf8e/aiohttp-3.11.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a1f7d857c4fcf7cabb1178058182c789b30d85de379e04f64c15b7e88d66fb", size = 453340 }, + { url = "https://files.pythonhosted.org/packages/2f/1c/b8010e4d65c5860d62681088e5376f3c0a940c5e3ca8989cae36ce8c3ea8/aiohttp-3.11.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba40b7ae0f81c7029583a338853f6607b6d83a341a3dcde8bed1ea58a3af1df9", size = 1665561 }, + { url = "https://files.pythonhosted.org/packages/19/ed/a68c3ab2f92fdc17dfc2096117d1cfaa7f7bdded2a57bacbf767b104165b/aiohttp-3.11.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5b95787335c483cd5f29577f42bbe027a412c5431f2f80a749c80d040f7ca9f", size = 1718335 }, + { url = "https://files.pythonhosted.org/packages/27/4f/3a0b6160ce663b8ebdb65d1eedff60900cd7108838c914d25952fe2b909f/aiohttp-3.11.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7d474c5c1f0b9405c1565fafdc4429fa7d986ccbec7ce55bc6a330f36409cad", size = 1775522 }, + { url = "https://files.pythonhosted.org/packages/0b/58/9da09291e19696c452e7224c1ce8c6d23a291fe8cd5c6b247b51bcda07db/aiohttp-3.11.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e83fb1991e9d8982b3b36aea1e7ad27ea0ce18c14d054c7a404d68b0319eebb", size = 1677566 }, + { url = "https://files.pythonhosted.org/packages/3d/18/6184f2bf8bbe397acbbbaa449937d61c20a6b85765f48e5eddc6d84957fe/aiohttp-3.11.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4586a68730bd2f2b04a83e83f79d271d8ed13763f64b75920f18a3a677b9a7f0", size = 1603590 }, + { url = "https://files.pythonhosted.org/packages/04/94/91e0d1ca0793012ccd927e835540aa38cca98bdce2389256ab813ebd64a3/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fe4eb0e7f50cdb99b26250d9328faef30b1175a5dbcfd6d0578d18456bac567", size = 1618688 }, + { url = "https://files.pythonhosted.org/packages/71/85/d13c3ea2e48a10b43668305d4903838834c3d4112e5229177fbcc23a56cd/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2a8a6bc19818ac3e5596310ace5aa50d918e1ebdcc204dc96e2f4d505d51740c", size = 1658053 }, + { url = "https://files.pythonhosted.org/packages/12/6a/3242a35100de23c1e8d9e05e8605e10f34268dee91b00d9d1e278c58eb80/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f27eec42f6c3c1df09cfc1f6786308f8b525b8efaaf6d6bd76c1f52c6511f6a", size = 1616917 }, + { url = "https://files.pythonhosted.org/packages/f5/b3/3f99b6f0a9a79590a7ba5655dbde8408c685aa462247378c977603464d0a/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2a4a13dfbb23977a51853b419141cd0a9b9573ab8d3a1455c6e63561387b52ff", size = 1685872 }, + { url = "https://files.pythonhosted.org/packages/8a/2e/99672181751f280a85e24fcb9a2c2469e8b1a0de1746b7b5c45d1eb9a999/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:02876bf2f69b062584965507b07bc06903c2dc93c57a554b64e012d636952654", size = 1715719 }, + { url = "https://files.pythonhosted.org/packages/7a/cd/68030356eb9a7d57b3e2823c8a852709d437abb0fbff41a61ebc351b7625/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b992778d95b60a21c4d8d4a5f15aaab2bd3c3e16466a72d7f9bfd86e8cea0d4b", size = 1673166 }, + { url = "https://files.pythonhosted.org/packages/03/61/425397a9a2839c609d09fdb53d940472f316a2dbeaa77a35b2628dae6284/aiohttp-3.11.13-cp313-cp313-win32.whl", hash = "sha256:507ab05d90586dacb4f26a001c3abf912eb719d05635cbfad930bdbeb469b36c", size = 410615 }, + { url = "https://files.pythonhosted.org/packages/9c/54/ebb815bc0fe057d8e7a11c086c479e972e827082f39aeebc6019dd4f0862/aiohttp-3.11.13-cp313-cp313-win_amd64.whl", hash = "sha256:5ceb81a4db2decdfa087381b5fc5847aa448244f973e5da232610304e199e7b2", size = 436452 }, + { url = "https://files.pythonhosted.org/packages/86/88/c80c0972d35cdce2a62905a2053fc483685bf5f3930f1ab269ec006e1e98/aiohttp-3.11.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:51c3ff9c7a25f3cad5c09d9aacbc5aefb9267167c4652c1eb737989b554fe278", size = 709814 }, + { url = "https://files.pythonhosted.org/packages/ca/e6/d7ee65a814615fb6de79d124bb72be4e84f9d68485751c5279994554f061/aiohttp-3.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e271beb2b1dabec5cd84eb488bdabf9758d22ad13471e9c356be07ad139b3012", size = 469313 }, + { url = "https://files.pythonhosted.org/packages/8c/ab/d6257596cad471675419673d53f6e409d9eb7acfa7e36dfb77e8b65504b3/aiohttp-3.11.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e9eb7e5764abcb49f0e2bd8f5731849b8728efbf26d0cac8e81384c95acec3f", size = 456376 }, + { url = "https://files.pythonhosted.org/packages/1d/d5/ab9ad5242c7920e224cbdc1c9bec62a79f75884049ccb86edb64225e4c0f/aiohttp-3.11.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baae005092e3f200de02699314ac8933ec20abf998ec0be39448f6605bce93df", size = 1587792 }, + { url = "https://files.pythonhosted.org/packages/23/01/ef79aeb337702bbfd034b1d1a6357dca4a270ebe2b0ff80bb8ba90851ea0/aiohttp-3.11.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1982c98ac62c132d2b773d50e2fcc941eb0b8bad3ec078ce7e7877c4d5a2dce7", size = 1636636 }, + { url = "https://files.pythonhosted.org/packages/a6/ff/3bc33d6ab85046ecc3319817c1f473061cd97caba5a1cd154be181ab56ab/aiohttp-3.11.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2b25b2eeb35707113b2d570cadc7c612a57f1c5d3e7bb2b13870fe284e08fc0", size = 1672707 }, + { url = "https://files.pythonhosted.org/packages/f4/fd/2d1934d22b89de0d6b9dbb30c310996e440fffc08f95b083d91b6a7916c1/aiohttp-3.11.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b27961d65639128336b7a7c3f0046dcc62a9443d5ef962e3c84170ac620cec47", size = 1589919 }, + { url = "https://files.pythonhosted.org/packages/35/01/b13fe945b056a910fe98f659e6533b4a9e7f08f414f6c5447a9726df81e0/aiohttp-3.11.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a01fe9f1e05025eacdd97590895e2737b9f851d0eb2e017ae9574d9a4f0b6252", size = 1544444 }, + { url = "https://files.pythonhosted.org/packages/73/9b/26da500b8de48a88b287936fae66d4f52306daedc6b6a273e97f479db685/aiohttp-3.11.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa1fb1b61881c8405829c50e9cc5c875bfdbf685edf57a76817dfb50643e4a1a", size = 1530616 }, + { url = "https://files.pythonhosted.org/packages/fc/27/5d1636c675f4f5ad0a8a68874d78fe6049041274d4d5da682f4ffee78097/aiohttp-3.11.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:25de43bb3cf83ad83efc8295af7310219af6dbe4c543c2e74988d8e9c8a2a917", size = 1559227 }, + { url = "https://files.pythonhosted.org/packages/32/cc/3ae7e23762b28fa9f794d89fde21111c5af85a2ec081a15812c312febfa7/aiohttp-3.11.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe7065e2215e4bba63dc00db9ae654c1ba3950a5fff691475a32f511142fcddb", size = 1536468 }, + { url = "https://files.pythonhosted.org/packages/cc/96/4ad817e79b0a3cc5089b818fccaf724d7d179f5840bc43fa538a2506f396/aiohttp-3.11.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7836587eef675a17d835ec3d98a8c9acdbeb2c1d72b0556f0edf4e855a25e9c1", size = 1607310 }, + { url = "https://files.pythonhosted.org/packages/3f/f3/c7e502478b8a181a85ac1524a6755dbb41959ee82edb681981733dcac87e/aiohttp-3.11.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:85fa0b18558eb1427090912bd456a01f71edab0872f4e0f9e4285571941e4090", size = 1629492 }, + { url = "https://files.pythonhosted.org/packages/3a/bb/0629e93af6317b277285a472d8e7aa92fa4e654dca00cf70f89f1788bd89/aiohttp-3.11.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a86dc177eb4c286c19d1823ac296299f59ed8106c9536d2b559f65836e0fb2c6", size = 1567741 }, + { url = "https://files.pythonhosted.org/packages/fc/40/427dafa3664413d29c5b3546aaacafb33e7725b1f6e15ce54cb857183c7b/aiohttp-3.11.13-cp39-cp39-win32.whl", hash = "sha256:684eea71ab6e8ade86b9021bb62af4bf0881f6be4e926b6b5455de74e420783a", size = 417303 }, + { url = "https://files.pythonhosted.org/packages/ca/a1/c7c0cdccbad4678dfb51f4d4f22dc6aacf8e3cdd6b99071170246106c364/aiohttp-3.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:82c249f2bfa5ecbe4a1a7902c81c0fba52ed9ebd0176ab3047395d02ad96cfcb", size = 442608 }, ] [[package]] @@ -241,7 +242,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.46.0" +version = "0.49.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -252,9 +253,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/68/3b4c045edf6dc6933895e8f279cc77c7684874c8aba46a4e6241c8b147cf/anthropic-0.46.0.tar.gz", hash = "sha256:eac3d43271d02321a57c3ca68aca84c3d58873e8e72d1433288adee2d46b745b", size = 202191 } +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/a88c8494ce4d1a88252b9e053607e885f9b14d0a32273d47b727cbee4228/anthropic-0.49.0.tar.gz", hash = "sha256:c09e885b0f674b9119b4f296d8508907f6cff0009bc20d5cf6b35936c40b4398", size = 210016 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/6f/346beae0375df5f6907230bc63d557ef5d7659be49250ac5931a758322ae/anthropic-0.46.0-py3-none-any.whl", hash = "sha256:1445ec9be78d2de7ea51b4d5acd3574e414aea97ef903d0ecbb57bec806aaa49", size = 223228 }, + { url = "https://files.pythonhosted.org/packages/76/74/5d90ad14d55fbe3f9c474fdcb6e34b4bed99e3be8efac98734a5ddce88c1/anthropic-0.49.0-py3-none-any.whl", hash = "sha256:bbc17ad4e7094988d2fa86b87753ded8dce12498f4b85fe5810f208f454a8375", size = 243368 }, ] [[package]] @@ -648,16 +649,16 @@ wheels = [ [[package]] name = "flake8" -version = "7.1.1" +version = "7.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mccabe" }, { name = "pycodestyle" }, { name = "pyflakes" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/72/e8d66150c4fcace3c0a450466aa3480506ba2cae7b61e100a2613afc3907/flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38", size = 48054 } +sdist = { url = "https://files.pythonhosted.org/packages/58/16/3f2a0bb700ad65ac9663262905a025917c020a3f92f014d2ba8964b4602c/flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd", size = 48119 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/42/65004373ac4617464f35ed15931b30d764f53cdd30cc78d5aea349c8c050/flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213", size = 57731 }, + { url = "https://files.pythonhosted.org/packages/35/f8/08d37b2cd89da306e3520bd27f8a85692122b42b56c0c2c3784ff09c022f/flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a", size = 57745 }, ] [[package]] @@ -788,7 +789,7 @@ wheels = [ [[package]] name = "google-ai-generativelanguage" -version = "0.6.15" +version = "0.6.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core", extra = ["grpc"] }, @@ -796,9 +797,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/d1/48fe5d7a43d278e9f6b5ada810b0a3530bbeac7ed7fcbcd366f932f05316/google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3", size = 1375443 } +sdist = { url = "https://files.pythonhosted.org/packages/7a/8b/cb2da099282cf1bf65e4695a1365166652fd3cf136ce6af2cf9129394a54/google_ai_generativelanguage-0.6.16.tar.gz", hash = "sha256:494f73c44dede1fd6853e579efe590f139d0654481d2a5bdadfc415ec5351d3d", size = 1418441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/a3/67b8a6ff5001a1d8864922f2d6488dc2a14367ceb651bc3f09a947f2f306/google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c", size = 1327356 }, + { url = "https://files.pythonhosted.org/packages/07/e5/b136691121ed600afc0327ffe3a5da3894774359f15520f33f1653c79a41/google_ai_generativelanguage-0.6.16-py3-none-any.whl", hash = "sha256:b53c736b8ebed75fe040d48740b0a15370d75e7dbc72249fb7acd2c9171bc072", size = 1353133 }, ] [[package]] @@ -823,22 +824,6 @@ grpc = [ { name = "grpcio-status" }, ] -[[package]] -name = "google-api-python-client" -version = "2.161.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/0a/50/c8d2d3c4e65e081c4c07b15e4fe35671676c5ecdb3674a167229e83ce49a/google_api_python_client-2.161.0.tar.gz", hash = "sha256:324c0cce73e9ea0a0d2afd5937e01b7c2d6a4d7e2579cdb6c384f9699d6c9f37", size = 12358839 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/e8/ca1efe224166a4c77ac92b4314b90f2fb70fdde1f763c1613ba3b9f50752/google_api_python_client-2.161.0-py2.py3-none-any.whl", hash = "sha256:9476a5a4f200bae368140453df40f9cda36be53fa7d0e9a9aac4cdb859a26448", size = 12869974 }, -] - [[package]] name = "google-auth" version = "2.38.0" @@ -853,37 +838,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/47/603554949a37bca5b7f894d51896a9c534b9eab808e2520a748e081669d0/google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a", size = 210770 }, ] -[[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 } -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 }, -] - -[[package]] -name = "google-generativeai" -version = "0.8.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-ai-generativelanguage" }, - { name = "google-api-core" }, - { name = "google-api-python-client" }, - { name = "google-auth" }, - { name = "protobuf" }, - { name = "pydantic" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/b0/6c6af327a8a6ef3be6fe79be1d6f1e2914d6c363aa6b081b93396f4460a7/google_generativeai-0.8.4-py3-none-any.whl", hash = "sha256:e987b33ea6decde1e69191ddcaec6ef974458864d243de7191db50c21a7c5b82", size = 175409 }, -] - [[package]] name = "googleapis-common-protos" version = "1.67.0" @@ -959,15 +913,16 @@ wheels = [ [[package]] name = "grep-ast" -version = "0.5.0" +version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pathspec" }, + { name = "tree-sitter" }, { name = "tree-sitter-languages" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/68/8626658faf9e9d15ff0336c8f057be934ae7ec35ceeeefc74bd8e05fe61a/grep_ast-0.5.0.tar.gz", hash = "sha256:24f7ecbb7615ba35039eecb8302b83bb7766dd3d77c5556894471726fafa64ed", size = 11292 } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5f/3d226aeaa4e788ffe9d028b18f4f29380cb37e6026f0872e5729ac7ac126/grep_ast-0.6.1.tar.gz", hash = "sha256:b904580a991497afd4135c51a217d001b27086323bc7529673a1dd4003ee24d0", size = 12284 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/85/d8425afe1a79836733c4b269b22c093562f5e474f1538f4c1fd01375cb7b/grep_ast-0.5.0-py3-none-any.whl", hash = "sha256:e655a7a6dec28a0d4848b58a4fdb043fb1b1953897dfcc85f391cea46fab8d42", size = 11906 }, + { url = "https://files.pythonhosted.org/packages/e3/cb/23f3cd837510fed606b333847753ec2102691f56f15b4e72a39a10145105/grep_ast-0.6.1-py3-none-any.whl", hash = "sha256:57832bb903ebd04e880775946c7a71af5c6a97be9404699caf9d5acc90430bca", size = 12249 }, ] [[package]] @@ -1059,18 +1014,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, ] -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854 }, -] - [[package]] name = "httpx" version = "0.28.1" @@ -1088,7 +1031,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "0.28.1" +version = "0.29.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -1099,9 +1042,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/a734204aaae6c35a22f9956ebcd8d8708ae5b842e15d6f42bd6f49e634a4/huggingface_hub-0.28.1.tar.gz", hash = "sha256:893471090c98e3b6efbdfdacafe4052b20b84d59866fb6f54c33d9af18c303ae", size = 387074 } +sdist = { url = "https://files.pythonhosted.org/packages/22/37/797d6476f13e5ef6af5fc48a5d641d32b39c37e166ccf40c3714c5854a85/huggingface_hub-0.29.1.tar.gz", hash = "sha256:9524eae42077b8ff4fc459ceb7a514eca1c1232b775276b009709fe2a084f250", size = 389776 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/da/6c2bea5327b640920267d3bf2c9fc114cfbd0a5de234d81cda80cc9e33c8/huggingface_hub-0.28.1-py3-none-any.whl", hash = "sha256:aa6b9a3ffdae939b72c464dbb0d7f99f56e649b55c3d52406f49e0a5a620c0a7", size = 464068 }, + { url = "https://files.pythonhosted.org/packages/ae/05/75b90de9093de0aadafc868bb2fa7c57651fd8f45384adf39bd77f63980d/huggingface_hub-0.29.1-py3-none-any.whl", hash = "sha256:352f69caf16566c7b6de84b54a822f6238e17ddd8ae3da4f8f2272aea5b198d5", size = 468049 }, ] [[package]] @@ -1310,21 +1253,21 @@ wheels = [ [[package]] name = "langchain-anthropic" -version = "0.3.7" +version = "0.3.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anthropic" }, { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/b0/84cfe0b4b829bcdc99fbb1a06973a6f3109b4e326292cdf5fa46f88dbf2f/langchain_anthropic-0.3.7.tar.gz", hash = "sha256:534cd1867bc41711cd8c3d0a0bc055e6c5a4215953c87260209a90dc5816f30d", size = 39838 } +sdist = { url = "https://files.pythonhosted.org/packages/be/0a/7ccb79c41575b04266fc4def50f41d0a4689361421d82a14350d9d5e783e/langchain_anthropic-0.3.9.tar.gz", hash = "sha256:e8012d7986ad1d8412df6914c56f3c0d2797f231766a03bb1ad22cc7023e6e1d", size = 42205 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/b3/111e1f41b0044687ec0c34c921ad52d33d2802282b1bc45343d5dd923fb6/langchain_anthropic-0.3.7-py3-none-any.whl", hash = "sha256:adec0a1daabd3c25249753c6cd625654917fb9e3feee68e72c7dc3f4449c0f3c", size = 22998 }, + { url = "https://files.pythonhosted.org/packages/b9/27/258565b4a487fca7db363ea95765e6f1f00c23baa83dc4ec19a009213658/langchain_anthropic-0.3.9-py3-none-any.whl", hash = "sha256:adbbfaf3ce9798d46fb43d6fc01105630238f375dc6043d35d0aafab61fdbb71", size = 24414 }, ] [[package]] name = "langchain-core" -version = "0.3.36" +version = "0.3.43" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1335,38 +1278,38 @@ dependencies = [ { name = "tenacity" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/41/e638f46eb7037fd8aab3484d9c109d0f30a04ac4bbd3e283dcfc80a31309/langchain_core-0.3.36.tar.gz", hash = "sha256:dffdce8a554905f53f33c1d6a40633a45a8d47c17c5792753891dd73941cd57a", size = 526843 } +sdist = { url = "https://files.pythonhosted.org/packages/8e/18/26255368f56d2749709fc2884c521d64471f32118ce09dfc677e0596be20/langchain_core-0.3.43.tar.gz", hash = "sha256:bec60f4f5665b536434ff747b8f23375a812e82cfa529f519b54cc1e7a94a875", size = 529403 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/06/b764bcf5523c271a35005ba7047f6d216337e598b41a1f2783a99a11f5d6/langchain_core-0.3.36-py3-none-any.whl", hash = "sha256:8410311862c7c674e4f3f120cfd8d1f3d003d6e7d8cb8f934746e222f7e865d9", size = 413640 }, + { url = "https://files.pythonhosted.org/packages/20/0e/ddf9f5dc46b178df5c101666bb3bc7fc526d68cd81cdd60cbe1b6b438b30/langchain_core-0.3.43-py3-none-any.whl", hash = "sha256:caa6bc1f4c6ab71d3c2e400f8b62e1cd6dc5ac2c37e03f12f3e2c60befd5b273", size = 415421 }, ] [[package]] name = "langchain-google-genai" -version = "2.0.9" +version = "2.0.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filetype" }, - { name = "google-generativeai" }, + { name = "google-ai-generativelanguage" }, { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/1f/2a275165ba5a455147472682db71ca4cc45e414cfb37c1245efe283d4f43/langchain_google_genai-2.0.9.tar.gz", hash = "sha256:65205089da1f72688a0ed6e7c6914af308b6514ab8038fd8126ecb20f1df234c", size = 37437 } +sdist = { url = "https://files.pythonhosted.org/packages/43/ef/1df5184057b3c49f0f67a8daf59e39b23cdc3267b613451389cd72b24729/langchain_google_genai-2.0.11.tar.gz", hash = "sha256:ff6997eee872f0732036129173f4c1740b03fbb1f13251805d51d6c08bf8b34d", size = 35444 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/6a/b6cbd72b820d31cf35fe677cc844220aa82a09b92167d43ade815acdec4d/langchain_google_genai-2.0.9-py3-none-any.whl", hash = "sha256:48d8c78c42048d54f40dff333db9d359746644e0feb0e08b5eabdf34ad7149ca", size = 41698 }, + { url = "https://files.pythonhosted.org/packages/c5/bc/233352abbf0d2a1ea2d7748915a8aac63accd2ce893ac0d42a86d529c823/langchain_google_genai-2.0.11-py3-none-any.whl", hash = "sha256:c98b18524a78fcc7084ba5ac69ea6a1a69b0b693255de68245b98bbbc3f08e87", size = 39792 }, ] [[package]] name = "langchain-openai" -version = "0.3.6" +version = "0.3.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "openai" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/67/4c2f371315bd1dd1163f3d1d48d271649e5c4b81b1982c38db3761b883a5/langchain_openai-0.3.6.tar.gz", hash = "sha256:7daf92e1cd98865ab5213ec5bec2cbd6c28f011e250714978b3a99c7e4fc88ce", size = 255792 } +sdist = { url = "https://files.pythonhosted.org/packages/2e/04/ae071af0b04d1c3a8040498714091afd21149f6f8ae1dbab584317d9dfd7/langchain_openai-0.3.8.tar.gz", hash = "sha256:4d73727eda8102d1d07a2ca036278fccab0bb5e0abf353cec9c3973eb72550ec", size = 256898 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/49/302754c09f955e4a240efe83e48f4e79149d50ca52b3f4731365f1be94b1/langchain_openai-0.3.6-py3-none-any.whl", hash = "sha256:05f0869f6cc963e2ec9e2e54ea1038d9c2af784c67f0e217040dfc918b31649a", size = 54930 }, + { url = "https://files.pythonhosted.org/packages/a5/43/9c6a1101bcd751d52a3328a06956f85122f9aaa31da1b15a8e0f99a70317/langchain_openai-0.3.8-py3-none-any.whl", hash = "sha256:9004dc8ef853aece0d8f0feca7753dc97f710fa3e53874c8db66466520436dbb", size = 55446 }, ] [[package]] @@ -1383,29 +1326,43 @@ wheels = [ [[package]] name = "langgraph" -version = "0.2.73" +version = "0.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, { name = "langgraph-sdk" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/80/545dd6253fe164b56cbfb85834ecd2fac2eed6be477f831429ab1d78bd4e/langgraph-0.2.73.tar.gz", hash = "sha256:61ae2b2140940d32543a384ebc96f5cca13c14932a377fba58e73257f9997de6", size = 131357 } +sdist = { url = "https://files.pythonhosted.org/packages/4e/fa/b1ecc95a2464bc7dbe5e67fbd21096013829119899c33236090b98c75508/langgraph-0.3.5.tar.gz", hash = "sha256:7c0d8e61aa02578b41036c9f7a599ccba2562d269f66ef76bacbba47a99a7eca", size = 114020 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/61/e4a148e0459e049a712f0bc92754c6e220b10c17f4b2aed6e712d7dd2ae8/langgraph-0.2.73-py3-none-any.whl", hash = "sha256:a6c1e491674a11b8d4f946cb7120ae0d18313f0daf6bb16cccd9d49ea547a780", size = 151472 }, + { url = "https://files.pythonhosted.org/packages/a4/5f/1e1d9173b5c41eff54f88d9f4ee82c38eb4928120ab6a21a68a78d1c499e/langgraph-0.3.5-py3-none-any.whl", hash = "sha256:be313ec300633c857873ea3e44aece4dd7d0b11f131d385108b359d377a85bf7", size = 131527 }, ] [[package]] name = "langgraph-checkpoint" -version = "2.0.16" +version = "2.0.18" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "msgpack" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/66/5d4a2013a84c511be289bb4a5ef91cbaad28c091b6b366fdb79710a1458b/langgraph_checkpoint-2.0.16.tar.gz", hash = "sha256:49ba8cfa12b2aae845ccc3b1fbd1d7a8d3a6c4a2e387ab3a92fca40dd3d4baa5", size = 34206 } +sdist = { url = "https://files.pythonhosted.org/packages/76/1d/27a178de8a40c0cd53671f6a7e9aa21967a17672fdc774e5c0ae6cc406a4/langgraph_checkpoint-2.0.18.tar.gz", hash = "sha256:2822eedd028b454b7bfebfb7e04347aed1b64db97dedb7eb68ef0fb42641606d", size = 34947 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/63/03bc3dd304ead45b53313cab8727329e1d139a2d220f2d030c72242c860e/langgraph_checkpoint-2.0.16-py3-none-any.whl", hash = "sha256:dfab51076a6eddb5f9e146cfe1b977e3dd6419168b2afa23ff3f4e47973bf06f", size = 38291 }, + { url = "https://files.pythonhosted.org/packages/21/11/91062b03b22b9ce6474df7c3e056417a4c2b029f9cc71829dd6f62479dd0/langgraph_checkpoint-2.0.18-py3-none-any.whl", hash = "sha256:941de442e5a893a6cabb8c3845f03159301b85f63ff4e8f2b308f7dfd96a3f59", size = 39106 }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/68/e1e692dbaeb4e9159b60a585fbfc26fbf073b3bb061caa2ff3153f85121a/langgraph_prebuilt-0.1.2.tar.gz", hash = "sha256:cfa7e54006d45e8f3d034ee88fa1d457c381bf6a2a0de0e64c5d3a776659e6d0", size = 23310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/2c/2fd70d557b7343f766f79dc8184b391f3417fc85b34dd04439cdd12dc2e1/langgraph_prebuilt-0.1.2-py3-none-any.whl", hash = "sha256:32028c4c4370576748e6c2e075cab1e13b5e3f2c196a390d71cacfb455212311", size = 24684 }, ] [[package]] @@ -1538,7 +1495,7 @@ wheels = [ [[package]] name = "litellm" -version = "1.60.6" +version = "1.62.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1553,9 +1510,9 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/5b/487cb3994346a0f4a2109fd43d87029e0604d06d42eda404b75b14249a2d/litellm-1.60.6.tar.gz", hash = "sha256:b9fdd38b482abc6b6d6afffa6fbf25912b70b1b34ca91a5c798aba2d81bef322", size = 6460683 } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/a671625824733f3b0718f9a72ad16bd3074f76f988378b99c03165543593/litellm-1.62.1.tar.gz", hash = "sha256:eee9cc40dc9c1da7e411af2f4ef145a67bb61702ae4e1218c1bc15b9e6404daa", size = 6586623 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/6c/b1b4cab0997808e0f1d48ce744cb47e2a03d8d203b889993271169814df8/litellm-1.60.6-py3-none-any.whl", hash = "sha256:7c2d61f5073c823aa7b069328fed34e61d0e9a1777f91e758c1770724d060578", size = 6762571 }, + { url = "https://files.pythonhosted.org/packages/a5/eb/090b227c8b81735226f311db732e7ad034720e787966511b298fdb892e13/litellm-1.62.1-py3-none-any.whl", hash = "sha256:f576358c72b477207d1f45ce5ac895ede7bd84377f6420a6b522909c829a79dc", size = 6895313 }, ] [[package]] @@ -1883,7 +1840,7 @@ wheels = [ [[package]] name = "openai" -version = "1.61.1" +version = "1.65.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1895,9 +1852,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/cf/61e71ce64cf0a38f029da0f9a5f10c9fa0e69a7a977b537126dac50adfea/openai-1.61.1.tar.gz", hash = "sha256:ce1851507218209961f89f3520e06726c0aa7d0512386f0f977e3ac3e4f2472e", size = 350784 } +sdist = { url = "https://files.pythonhosted.org/packages/f6/03/0bbf201a7e44920d892db0445874c8111be4255cb9495379df18d6d36ea1/openai-1.65.2.tar.gz", hash = "sha256:729623efc3fd91c956f35dd387fa5c718edd528c4bed9f00b40ef290200fb2ce", size = 359185 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/b6/2e2a011b2dc27a6711376808b4cd8c922c476ea0f1420b39892117fa8563/openai-1.61.1-py3-none-any.whl", hash = "sha256:72b0826240ce26026ac2cd17951691f046e5be82ad122d20a8e1b30ca18bd11e", size = 463126 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/722ed868cb56f70264190ed479b38b3e46d14daa267d559a3fe3bd9061cf/openai-1.65.2-py3-none-any.whl", hash = "sha256:27d9fe8de876e31394c2553c4e6226378b6ed85e480f586ccfe25b7193fb1750", size = 473206 }, ] [[package]] @@ -1991,6 +1948,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] +[[package]] +name = "peewee" +version = "3.17.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/09/4393bd378e70b7fc3163ee83353cc27bb520010a5c2b3c924121e7e7e068/peewee-3.17.9.tar.gz", hash = "sha256:fe15cd001758e324c8e3ca8c8ed900e7397c2907291789e1efc383e66b9bc7a8", size = 3026085 } + +[[package]] +name = "peewee-migrate" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "peewee" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/66/8d5ad45ea396623533a9cb2bbf78fdcc52efb65f7db058d7dc2523e1dd41/peewee_migrate-1.13.0.tar.gz", hash = "sha256:1ab67f72a0936006155e1b310c18a32f79e4dff3917cfeb10112ca92518721e5", size = 17119 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/f9/bf657b918756b0b15d85845b351c0074b1d953798b8c72c05b4a456baf06/peewee_migrate-1.13.0-py3-none-any.whl", hash = "sha256:66597f5b8549a8ff456915db60e8382daf7839eef79352027e7cf54feec56860", size = 19425 }, +] + [[package]] name = "pexpect" version = "4.9.0" @@ -2082,11 +2058,20 @@ wheels = [ [[package]] name = "pip" -version = "25.0" +version = "25.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/3e/68beeeeb306ea20ffd30b3ed993f531d16cd884ec4f60c9b1e238f69f2af/pip-25.0.tar.gz", hash = "sha256:8e0a97f7b4c47ae4a494560da84775e9e2f671d415d8d828e052efefb206b30b", size = 1950328 } +sdist = { url = "https://files.pythonhosted.org/packages/70/53/b309b4a497b09655cb7e07088966881a57d082f48ac3cb54ea729fd2c6cf/pip-25.0.1.tar.gz", hash = "sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea", size = 1950850 } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/8a/1ddf40be20103bcc605db840e9ade09c8e8c9f920a03e9cfe88eae97a058/pip-25.0-py3-none-any.whl", hash = "sha256:b6eb97a803356a52b2dd4bb73ba9e65b2ba16caa6bcb25a7497350a4e5859b65", size = 1841506 }, + { url = "https://files.pythonhosted.org/packages/c9/bc/b7db44f5f39f9d0494071bddae6880eb645970366d0a200022a1a93d57f5/pip-25.0.1-py3-none-any.whl", hash = "sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f", size = 1841526 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, ] [[package]] @@ -2100,18 +2085,19 @@ wheels = [ [[package]] name = "posthog" -version = "3.11.0" +version = "3.18.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff" }, + { name = "distro" }, { name = "monotonic" }, { name = "python-dateutil" }, { name = "requests" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/f9/ffb682dfcfe43ff38c501791b8b4c01ba25f772c5d16bdb8c0f992f099fd/posthog-3.11.0.tar.gz", hash = "sha256:42a1f88cbcddeceaf6e8900a528db62d84fc56f6e5809f3d6dfb40e6f743091e", size = 61344 } +sdist = { url = "https://files.pythonhosted.org/packages/a5/1c/aa6bb26491108e9e350cd7af4d4b0a54d48c755cc76b2c2d90ef2916b8b3/posthog-3.18.1.tar.gz", hash = "sha256:ce115b8422f26c57cd4143499115b741f5683c93d0b5b87bab391579aaef084b", size = 65573 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/21/a7975b832603fed31930860108e12f7680ad829d74ce05eab2df1a17ae2d/posthog-3.11.0-py2.py3-none-any.whl", hash = "sha256:8cbd52c26bcdfbe65c4ea84a8090cfa2e046879d6b6d71da68e279a5b4aedb46", size = 72005 }, + { url = "https://files.pythonhosted.org/packages/04/c2/407c8cf3edf4fe33b82de3fee11178d083ee0b6e3eb28ff8072caaa85907/posthog-3.18.1-py2.py3-none-any.whl", hash = "sha256:6865104b7cf3a5b13949e2bc2aab9b37b5fbf5f9e045fa55b9eabe21b3850200", size = 76762 }, ] [[package]] @@ -2128,91 +2114,107 @@ wheels = [ [[package]] name = "propcache" -version = "0.2.1" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } +sdist = { url = "https://files.pythonhosted.org/packages/92/76/f941e63d55c0293ff7829dd21e7cf1147e90a526756869a9070f287a68c9/propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5", size = 42722 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/a5/0ea64c9426959ef145a938e38c832fc551843481d356713ececa9a8a64e8/propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6", size = 79296 }, - { url = "https://files.pythonhosted.org/packages/76/5a/916db1aba735f55e5eca4733eea4d1973845cf77dfe67c2381a2ca3ce52d/propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2", size = 45622 }, - { url = "https://files.pythonhosted.org/packages/2d/62/685d3cf268b8401ec12b250b925b21d152b9d193b7bffa5fdc4815c392c2/propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea", size = 45133 }, - { url = "https://files.pythonhosted.org/packages/4d/3d/31c9c29ee7192defc05aa4d01624fd85a41cf98e5922aaed206017329944/propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212", size = 204809 }, - { url = "https://files.pythonhosted.org/packages/10/a1/e4050776f4797fc86140ac9a480d5dc069fbfa9d499fe5c5d2fa1ae71f07/propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3", size = 219109 }, - { url = "https://files.pythonhosted.org/packages/c9/c0/e7ae0df76343d5e107d81e59acc085cea5fd36a48aa53ef09add7503e888/propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d", size = 217368 }, - { url = "https://files.pythonhosted.org/packages/fc/e1/e0a2ed6394b5772508868a977d3238f4afb2eebaf9976f0b44a8d347ad63/propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634", size = 205124 }, - { url = "https://files.pythonhosted.org/packages/50/c1/e388c232d15ca10f233c778bbdc1034ba53ede14c207a72008de45b2db2e/propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2", size = 195463 }, - { url = "https://files.pythonhosted.org/packages/0a/fd/71b349b9def426cc73813dbd0f33e266de77305e337c8c12bfb0a2a82bfb/propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958", size = 198358 }, - { url = "https://files.pythonhosted.org/packages/02/f2/d7c497cd148ebfc5b0ae32808e6c1af5922215fe38c7a06e4e722fe937c8/propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c", size = 195560 }, - { url = "https://files.pythonhosted.org/packages/bb/57/f37041bbe5e0dfed80a3f6be2612a3a75b9cfe2652abf2c99bef3455bbad/propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583", size = 196895 }, - { url = "https://files.pythonhosted.org/packages/83/36/ae3cc3e4f310bff2f064e3d2ed5558935cc7778d6f827dce74dcfa125304/propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf", size = 207124 }, - { url = "https://files.pythonhosted.org/packages/8c/c4/811b9f311f10ce9d31a32ff14ce58500458443627e4df4ae9c264defba7f/propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034", size = 210442 }, - { url = "https://files.pythonhosted.org/packages/18/dd/a1670d483a61ecac0d7fc4305d91caaac7a8fc1b200ea3965a01cf03bced/propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b", size = 203219 }, - { url = "https://files.pythonhosted.org/packages/f9/2d/30ced5afde41b099b2dc0c6573b66b45d16d73090e85655f1a30c5a24e07/propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4", size = 40313 }, - { url = "https://files.pythonhosted.org/packages/23/84/bd9b207ac80da237af77aa6e153b08ffa83264b1c7882495984fcbfcf85c/propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba", size = 44428 }, - { url = "https://files.pythonhosted.org/packages/bc/0f/2913b6791ebefb2b25b4efd4bb2299c985e09786b9f5b19184a88e5778dd/propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16", size = 79297 }, - { url = "https://files.pythonhosted.org/packages/cf/73/af2053aeccd40b05d6e19058419ac77674daecdd32478088b79375b9ab54/propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717", size = 45611 }, - { url = "https://files.pythonhosted.org/packages/3c/09/8386115ba7775ea3b9537730e8cf718d83bbf95bffe30757ccf37ec4e5da/propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3", size = 45146 }, - { url = "https://files.pythonhosted.org/packages/03/7a/793aa12f0537b2e520bf09f4c6833706b63170a211ad042ca71cbf79d9cb/propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9", size = 232136 }, - { url = "https://files.pythonhosted.org/packages/f1/38/b921b3168d72111769f648314100558c2ea1d52eb3d1ba7ea5c4aa6f9848/propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787", size = 239706 }, - { url = "https://files.pythonhosted.org/packages/14/29/4636f500c69b5edea7786db3c34eb6166f3384b905665ce312a6e42c720c/propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465", size = 238531 }, - { url = "https://files.pythonhosted.org/packages/85/14/01fe53580a8e1734ebb704a3482b7829a0ef4ea68d356141cf0994d9659b/propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af", size = 231063 }, - { url = "https://files.pythonhosted.org/packages/33/5c/1d961299f3c3b8438301ccfbff0143b69afcc30c05fa28673cface692305/propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7", size = 220134 }, - { url = "https://files.pythonhosted.org/packages/00/d0/ed735e76db279ba67a7d3b45ba4c654e7b02bc2f8050671ec365d8665e21/propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f", size = 220009 }, - { url = "https://files.pythonhosted.org/packages/75/90/ee8fab7304ad6533872fee982cfff5a53b63d095d78140827d93de22e2d4/propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54", size = 212199 }, - { url = "https://files.pythonhosted.org/packages/eb/ec/977ffaf1664f82e90737275873461695d4c9407d52abc2f3c3e24716da13/propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505", size = 214827 }, - { url = "https://files.pythonhosted.org/packages/57/48/031fb87ab6081764054821a71b71942161619549396224cbb242922525e8/propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82", size = 228009 }, - { url = "https://files.pythonhosted.org/packages/1a/06/ef1390f2524850838f2390421b23a8b298f6ce3396a7cc6d39dedd4047b0/propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca", size = 231638 }, - { url = "https://files.pythonhosted.org/packages/38/2a/101e6386d5a93358395da1d41642b79c1ee0f3b12e31727932b069282b1d/propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e", size = 222788 }, - { url = "https://files.pythonhosted.org/packages/db/81/786f687951d0979007e05ad9346cd357e50e3d0b0f1a1d6074df334b1bbb/propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034", size = 40170 }, - { url = "https://files.pythonhosted.org/packages/cf/59/7cc7037b295d5772eceb426358bb1b86e6cab4616d971bd74275395d100d/propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3", size = 44404 }, - { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, - { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, - { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, - { url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 }, - { url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 }, - { url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 }, - { url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 }, - { url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 }, - { url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 }, - { url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 }, - { url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 }, - { url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 }, - { url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 }, - { url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 }, - { url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 }, - { url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 }, - { url = "https://files.pythonhosted.org/packages/0f/2a/329e0547cf2def8857157f9477669043e75524cc3e6251cef332b3ff256f/propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc", size = 77002 }, - { url = "https://files.pythonhosted.org/packages/12/2d/c4df5415e2382f840dc2ecbca0eeb2293024bc28e57a80392f2012b4708c/propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9", size = 44639 }, - { url = "https://files.pythonhosted.org/packages/d0/5a/21aaa4ea2f326edaa4e240959ac8b8386ea31dedfdaa636a3544d9e7a408/propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439", size = 44049 }, - { url = "https://files.pythonhosted.org/packages/4e/3e/021b6cd86c0acc90d74784ccbb66808b0bd36067a1bf3e2deb0f3845f618/propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536", size = 224819 }, - { url = "https://files.pythonhosted.org/packages/3c/57/c2fdeed1b3b8918b1770a133ba5c43ad3d78e18285b0c06364861ef5cc38/propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629", size = 229625 }, - { url = "https://files.pythonhosted.org/packages/9d/81/70d4ff57bf2877b5780b466471bebf5892f851a7e2ca0ae7ffd728220281/propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b", size = 232934 }, - { url = "https://files.pythonhosted.org/packages/3c/b9/bb51ea95d73b3fb4100cb95adbd4e1acaf2cbb1fd1083f5468eeb4a099a8/propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052", size = 227361 }, - { url = "https://files.pythonhosted.org/packages/f1/20/3c6d696cd6fd70b29445960cc803b1851a1131e7a2e4ee261ee48e002bcd/propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce", size = 213904 }, - { url = "https://files.pythonhosted.org/packages/a1/cb/1593bfc5ac6d40c010fa823f128056d6bc25b667f5393781e37d62f12005/propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d", size = 212632 }, - { url = "https://files.pythonhosted.org/packages/6d/5c/e95617e222be14a34c709442a0ec179f3207f8a2b900273720501a70ec5e/propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce", size = 207897 }, - { url = "https://files.pythonhosted.org/packages/8e/3b/56c5ab3dc00f6375fbcdeefdede5adf9bee94f1fab04adc8db118f0f9e25/propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95", size = 208118 }, - { url = "https://files.pythonhosted.org/packages/86/25/d7ef738323fbc6ebcbce33eb2a19c5e07a89a3df2fded206065bd5e868a9/propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf", size = 217851 }, - { url = "https://files.pythonhosted.org/packages/b3/77/763e6cef1852cf1ba740590364ec50309b89d1c818e3256d3929eb92fabf/propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f", size = 222630 }, - { url = "https://files.pythonhosted.org/packages/4f/e9/0f86be33602089c701696fbed8d8c4c07b6ee9605c5b7536fd27ed540c5b/propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30", size = 216269 }, - { url = "https://files.pythonhosted.org/packages/cc/02/5ac83217d522394b6a2e81a2e888167e7ca629ef6569a3f09852d6dcb01a/propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6", size = 39472 }, - { url = "https://files.pythonhosted.org/packages/f4/33/d6f5420252a36034bc8a3a01171bc55b4bff5df50d1c63d9caa50693662f/propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1", size = 43363 }, - { url = "https://files.pythonhosted.org/packages/0a/08/6ab7f65240a16fa01023125e65258acf7e4884f483f267cdd6fcc48f37db/propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541", size = 80403 }, - { url = "https://files.pythonhosted.org/packages/34/fe/e7180285e21b4e6dff7d311fdf22490c9146a09a02834b5232d6248c6004/propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e", size = 46152 }, - { url = "https://files.pythonhosted.org/packages/9c/36/aa74d884af826030ba9cee2ac109b0664beb7e9449c315c9c44db99efbb3/propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4", size = 45674 }, - { url = "https://files.pythonhosted.org/packages/22/59/6fe80a3fe7720f715f2c0f6df250dacbd7cad42832410dbd84c719c52f78/propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097", size = 207792 }, - { url = "https://files.pythonhosted.org/packages/4a/68/584cd51dd8f4d0f5fff5b128ce0cdb257cde903898eecfb92156bbc2c780/propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd", size = 223280 }, - { url = "https://files.pythonhosted.org/packages/85/cb/4c3528460c41e61b06ec3f970c0f89f87fa21f63acac8642ed81a886c164/propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681", size = 221293 }, - { url = "https://files.pythonhosted.org/packages/69/c0/560e050aa6d31eeece3490d1174da508f05ab27536dfc8474af88b97160a/propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16", size = 208259 }, - { url = "https://files.pythonhosted.org/packages/0c/87/d6c86a77632eb1ba86a328e3313159f246e7564cb5951e05ed77555826a0/propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d", size = 198632 }, - { url = "https://files.pythonhosted.org/packages/3a/2b/3690ea7b662dc762ab7af5f3ef0e2d7513c823d193d7b2a1b4cda472c2be/propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae", size = 203516 }, - { url = "https://files.pythonhosted.org/packages/4d/b5/afe716c16c23c77657185c257a41918b83e03993b6ccdfa748e5e7d328e9/propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b", size = 199402 }, - { url = "https://files.pythonhosted.org/packages/a4/c0/2d2df3aa7f8660d0d4cc4f1e00490c48d5958da57082e70dea7af366f876/propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347", size = 200528 }, - { url = "https://files.pythonhosted.org/packages/21/c8/65ac9142f5e40c8497f7176e71d18826b09e06dd4eb401c9a4ee41aa9c74/propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf", size = 211254 }, - { url = "https://files.pythonhosted.org/packages/09/e4/edb70b447a1d8142df51ec7511e84aa64d7f6ce0a0fdf5eb55363cdd0935/propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04", size = 214589 }, - { url = "https://files.pythonhosted.org/packages/cb/02/817f309ec8d8883287781d6d9390f80b14db6e6de08bc659dfe798a825c2/propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587", size = 207283 }, - { url = "https://files.pythonhosted.org/packages/d7/fe/2d18612096ed2212cfef821b6fccdba5d52efc1d64511c206c5c16be28fd/propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb", size = 40866 }, - { url = "https://files.pythonhosted.org/packages/24/2e/b5134802e7b57c403c7b73c7a39374e7a6b7f128d1968b4a4b4c0b700250/propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1", size = 44975 }, - { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/dc9ec44d2e63c13f816a16398c039329736712440ff82b682dd9a78d2258/propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d", size = 79574 }, + { url = "https://files.pythonhosted.org/packages/99/3a/33a207dfcb3ee1131ea23a2aeb726c3c4994f89546d7eadf8c50627c8b63/propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c", size = 45898 }, + { url = "https://files.pythonhosted.org/packages/af/68/0bde765c9f5dc02b4466d2838600af38c81b184c26c6d3cd44643ac668e3/propcache-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e7420211f5a65a54675fd860ea04173cde60a7cc20ccfbafcccd155225f8bc", size = 45418 }, + { url = "https://files.pythonhosted.org/packages/06/a6/c682669bae41199358e16cc7b1c818f91c5f9e925cc863dabd98ce32716a/propcache-0.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3302c5287e504d23bb0e64d2a921d1eb4a03fb93a0a0aa3b53de059f5a5d737d", size = 205116 }, + { url = "https://files.pythonhosted.org/packages/fb/ae/82cfb50267d9a1baa0340728eb9e32245a68538fef929d7bb786d01c11a8/propcache-0.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2e068a83552ddf7a39a99488bcba05ac13454fb205c847674da0352602082f", size = 219405 }, + { url = "https://files.pythonhosted.org/packages/ab/16/7b6b2bf8c207cfd0e5ca3d41aea397392de9899867ec024f88c94f9ae2ab/propcache-0.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d913d36bdaf368637b4f88d554fb9cb9d53d6920b9c5563846555938d5450bf", size = 217656 }, + { url = "https://files.pythonhosted.org/packages/f4/eb/41447de61eb5454891658d0fb9b1d7d35d49a4a5dd2e0c86f2c332e8b7e1/propcache-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ee1983728964d6070ab443399c476de93d5d741f71e8f6e7880a065f878e0b9", size = 205414 }, + { url = "https://files.pythonhosted.org/packages/03/b6/9719878f8b5b20d37ee663a40f8dcbf888559e4d3be2ba2fe5c790fc28d2/propcache-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36ca5e9a21822cc1746023e88f5c0af6fce3af3b85d4520efb1ce4221bed75cc", size = 195746 }, + { url = "https://files.pythonhosted.org/packages/bb/ec/b79c3210ba459800d1a8f1afeb81d7b503893555a7b79c24082ff26d3314/propcache-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9ecde3671e62eeb99e977f5221abcf40c208f69b5eb986b061ccec317c82ebd0", size = 198651 }, + { url = "https://files.pythonhosted.org/packages/48/f6/2b0140bc47013e43575973068e72ad51ee9f22f2dad42e6d6e362d715125/propcache-0.3.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d383bf5e045d7f9d239b38e6acadd7b7fdf6c0087259a84ae3475d18e9a2ae8b", size = 195858 }, + { url = "https://files.pythonhosted.org/packages/97/3d/2fa19303d87aa21f9a42dcd870d6088a2a776ff5518e394d50412c3679a6/propcache-0.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8cb625bcb5add899cb8ba7bf716ec1d3e8f7cdea9b0713fa99eadf73b6d4986f", size = 197181 }, + { url = "https://files.pythonhosted.org/packages/09/f3/a2170ffc9fa774c1dfd52294113c0fa6cdc5b71dbfd7129bb9378fdd8b42/propcache-0.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5fa159dcee5dba00c1def3231c249cf261185189205073bde13797e57dd7540a", size = 207411 }, + { url = "https://files.pythonhosted.org/packages/d6/1e/cb8a6c82178efffa0b00dc463f36cd086f747345585140aeb95d5cb93666/propcache-0.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7080b0159ce05f179cfac592cda1a82898ca9cd097dacf8ea20ae33474fbb25", size = 210724 }, + { url = "https://files.pythonhosted.org/packages/2b/72/6e273543337a3e22cf462eb836f065a9830b4d41baeb1f58db2695c934f3/propcache-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7161bccab7696a473fe7ddb619c1d75963732b37da4618ba12e60899fefe4f", size = 203511 }, + { url = "https://files.pythonhosted.org/packages/f3/ea/7412c79bcec06597c967d49789f5a1f7fd76a8654908feeaefafb7447c9a/propcache-0.3.0-cp310-cp310-win32.whl", hash = "sha256:bf0d9a171908f32d54f651648c7290397b8792f4303821c42a74e7805bfb813c", size = 40600 }, + { url = "https://files.pythonhosted.org/packages/a3/42/488c90190491f3e61bd2c2fb0b3d91c1c78778270dde2f0b6633fc9ff723/propcache-0.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:42924dc0c9d73e49908e35bbdec87adedd651ea24c53c29cac103ede0ea1d340", size = 44714 }, + { url = "https://files.pythonhosted.org/packages/45/c9/cf09ff7e6d09f14149094f7cd50d2dec032b24e61af21fc4540da2b17bfb/propcache-0.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51", size = 79568 }, + { url = "https://files.pythonhosted.org/packages/c8/32/2424d89da88cd81b7d148e0d2b3131461b570a02aa9d84a2e567509adb0d/propcache-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e", size = 45895 }, + { url = "https://files.pythonhosted.org/packages/f6/91/ee5b6aa7aa31754fefcf0c5180e09223cac380ef195c4ddc8c266eb641ea/propcache-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa", size = 45427 }, + { url = "https://files.pythonhosted.org/packages/bf/73/38f0128462b8b616181d8c53bd5d04eac41c50c449b07615c65d56ba0a9b/propcache-0.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf", size = 232427 }, + { url = "https://files.pythonhosted.org/packages/59/82/f3d4e84f4539dcfc9c3d338282b9e915f5b63c921986ecfdf7af2d12f87c/propcache-0.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b", size = 239985 }, + { url = "https://files.pythonhosted.org/packages/42/e8/029f58cccbae83c9969a7ee7a06558d5b83a93dfc54e0f4f70234bbaea1b/propcache-0.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9", size = 238827 }, + { url = "https://files.pythonhosted.org/packages/8b/a2/c373561777c0cb9b9e7b9b9a10b9b3a7b6bde75a2535b962231cecc8fdb8/propcache-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6", size = 231348 }, + { url = "https://files.pythonhosted.org/packages/d7/d2/4673f715beedf6038b485bcd976813149231d9df5bb6196cb69a09c185c9/propcache-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c", size = 220426 }, + { url = "https://files.pythonhosted.org/packages/e0/f6/1da65f900927bafd4675a16e890618ec7643f2f922bf0e4d84bb38645618/propcache-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075", size = 220294 }, + { url = "https://files.pythonhosted.org/packages/ff/86/620451bdc02e91b1712cd71890c17077ee97e2a28493836a87e47b8e70ff/propcache-0.3.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c", size = 212492 }, + { url = "https://files.pythonhosted.org/packages/6e/1b/e8f86921ed4016da80faf3b8f515f7829decabdbff106736bfff353bceba/propcache-0.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810", size = 215113 }, + { url = "https://files.pythonhosted.org/packages/1a/95/a61d86cc49aa0945f6c06f3a4614fc543e311a50558c92861f5e9691a37c/propcache-0.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3", size = 228330 }, + { url = "https://files.pythonhosted.org/packages/8f/7d/10dbae48ff2bb189e92c2b3487a48f3229146a25941ad0d485934d1104d4/propcache-0.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7", size = 231942 }, + { url = "https://files.pythonhosted.org/packages/39/ce/82d16aec96c5513ae7db13ab901a65a1e54c915292fb5b2390e33275b61d/propcache-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c", size = 223077 }, + { url = "https://files.pythonhosted.org/packages/c8/e0/cb077e8e7a583c733df7f53327fcbdb92e42be59b976ce60bf1d904a0efe/propcache-0.3.0-cp311-cp311-win32.whl", hash = "sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d", size = 40455 }, + { url = "https://files.pythonhosted.org/packages/d8/35/57abeb6146fe3c19081eeaf3d9d4cfea256f87f1e5101acf80d3332c1820/propcache-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32", size = 44705 }, + { url = "https://files.pythonhosted.org/packages/8d/2c/921f15dc365796ec23975b322b0078eae72995c7b4d49eba554c6a308d70/propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e", size = 79867 }, + { url = "https://files.pythonhosted.org/packages/11/a5/4a6cc1a559d1f2fb57ea22edc4245158cdffae92f7f92afcee2913f84417/propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af", size = 46109 }, + { url = "https://files.pythonhosted.org/packages/e1/6d/28bfd3af3a567ad7d667348e7f46a520bda958229c4d545ba138a044232f/propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5", size = 45635 }, + { url = "https://files.pythonhosted.org/packages/73/20/d75b42eaffe5075eac2f4e168f6393d21c664c91225288811d85451b2578/propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b", size = 242159 }, + { url = "https://files.pythonhosted.org/packages/a5/fb/4b537dd92f9fd4be68042ec51c9d23885ca5fafe51ec24c58d9401034e5f/propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667", size = 248163 }, + { url = "https://files.pythonhosted.org/packages/e7/af/8a9db04ac596d531ca0ef7dde518feaadfcdabef7b17d6a5ec59ee3effc2/propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7", size = 248794 }, + { url = "https://files.pythonhosted.org/packages/9d/c4/ecfc988879c0fd9db03228725b662d76cf484b6b46f7e92fee94e4b52490/propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7", size = 243912 }, + { url = "https://files.pythonhosted.org/packages/04/a2/298dd27184faa8b7d91cc43488b578db218b3cc85b54d912ed27b8c5597a/propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf", size = 229402 }, + { url = "https://files.pythonhosted.org/packages/be/0d/efe7fec316ca92dbf4bc4a9ba49ca889c43ca6d48ab1d6fa99fc94e5bb98/propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138", size = 226896 }, + { url = "https://files.pythonhosted.org/packages/60/63/72404380ae1d9c96d96e165aa02c66c2aae6072d067fc4713da5cde96762/propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86", size = 221447 }, + { url = "https://files.pythonhosted.org/packages/9d/18/b8392cab6e0964b67a30a8f4dadeaff64dc7022b5a34bb1d004ea99646f4/propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d", size = 222440 }, + { url = "https://files.pythonhosted.org/packages/6f/be/105d9ceda0f97eff8c06bac1673448b2db2a497444de3646464d3f5dc881/propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e", size = 234104 }, + { url = "https://files.pythonhosted.org/packages/cb/c9/f09a4ec394cfcce4053d8b2a04d622b5f22d21ba9bb70edd0cad061fa77b/propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64", size = 239086 }, + { url = "https://files.pythonhosted.org/packages/ea/aa/96f7f9ed6def82db67c972bdb7bd9f28b95d7d98f7e2abaf144c284bf609/propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c", size = 230991 }, + { url = "https://files.pythonhosted.org/packages/5a/11/bee5439de1307d06fad176f7143fec906e499c33d7aff863ea8428b8e98b/propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d", size = 40337 }, + { url = "https://files.pythonhosted.org/packages/e4/17/e5789a54a0455a61cb9efc4ca6071829d992220c2998a27c59aeba749f6f/propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57", size = 44404 }, + { url = "https://files.pythonhosted.org/packages/3a/0f/a79dd23a0efd6ee01ab0dc9750d8479b343bfd0c73560d59d271eb6a99d4/propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568", size = 77287 }, + { url = "https://files.pythonhosted.org/packages/b8/51/76675703c90de38ac75adb8deceb3f3ad99b67ff02a0fa5d067757971ab8/propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9", size = 44923 }, + { url = "https://files.pythonhosted.org/packages/01/9b/fd5ddbee66cf7686e73c516227c2fd9bf471dbfed0f48329d095ea1228d3/propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767", size = 44325 }, + { url = "https://files.pythonhosted.org/packages/13/1c/6961f11eb215a683b34b903b82bde486c606516c1466bf1fa67f26906d51/propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8", size = 225116 }, + { url = "https://files.pythonhosted.org/packages/ef/ea/f8410c40abcb2e40dffe9adeed017898c930974650a63e5c79b886aa9f73/propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0", size = 229905 }, + { url = "https://files.pythonhosted.org/packages/ef/5a/a9bf90894001468bf8e6ea293bb00626cc9ef10f8eb7996e9ec29345c7ed/propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d", size = 233221 }, + { url = "https://files.pythonhosted.org/packages/dd/ce/fffdddd9725b690b01d345c1156b4c2cc6dca09ab5c23a6d07b8f37d6e2f/propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05", size = 227627 }, + { url = "https://files.pythonhosted.org/packages/58/ae/45c89a5994a334735a3032b48e8e4a98c05d9536ddee0719913dc27da548/propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe", size = 214217 }, + { url = "https://files.pythonhosted.org/packages/01/84/bc60188c3290ff8f5f4a92b9ca2d93a62e449c8daf6fd11ad517ad136926/propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1", size = 212921 }, + { url = "https://files.pythonhosted.org/packages/14/b3/39d60224048feef7a96edabb8217dc3f75415457e5ebbef6814f8b2a27b5/propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92", size = 208200 }, + { url = "https://files.pythonhosted.org/packages/9d/b3/0a6720b86791251273fff8a01bc8e628bc70903513bd456f86cde1e1ef84/propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787", size = 208400 }, + { url = "https://files.pythonhosted.org/packages/e9/4f/bb470f3e687790547e2e78105fb411f54e0cdde0d74106ccadd2521c6572/propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545", size = 218116 }, + { url = "https://files.pythonhosted.org/packages/34/71/277f7f9add469698ac9724c199bfe06f85b199542121a71f65a80423d62a/propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e", size = 222911 }, + { url = "https://files.pythonhosted.org/packages/92/e3/a7b9782aef5a2fc765b1d97da9ec7aed2f25a4e985703608e73232205e3f/propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626", size = 216563 }, + { url = "https://files.pythonhosted.org/packages/ab/76/0583ca2c551aa08ffcff87b2c6849c8f01c1f6fb815a5226f0c5c202173e/propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374", size = 39763 }, + { url = "https://files.pythonhosted.org/packages/80/ec/c6a84f9a36f608379b95f0e786c111d5465926f8c62f12be8cdadb02b15c/propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a", size = 43650 }, + { url = "https://files.pythonhosted.org/packages/ee/95/7d32e3560f5bf83fc2f2a4c1b0c181d327d53d5f85ebd045ab89d4d97763/propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf", size = 82140 }, + { url = "https://files.pythonhosted.org/packages/86/89/752388f12e6027a5e63f5d075f15291ded48e2d8311314fff039da5a9b11/propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0", size = 47296 }, + { url = "https://files.pythonhosted.org/packages/1b/4c/b55c98d586c69180d3048984a57a5ea238bdeeccf82dbfcd598e935e10bb/propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829", size = 46724 }, + { url = "https://files.pythonhosted.org/packages/0f/b6/67451a437aed90c4e951e320b5b3d7eb584ade1d5592f6e5e8f678030989/propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa", size = 291499 }, + { url = "https://files.pythonhosted.org/packages/ee/ff/e4179facd21515b24737e1e26e02615dfb5ed29416eed4cf5bc6ac5ce5fb/propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6", size = 293911 }, + { url = "https://files.pythonhosted.org/packages/76/8d/94a8585992a064a23bd54f56c5e58c3b8bf0c0a06ae10e56f2353ae16c3d/propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db", size = 293301 }, + { url = "https://files.pythonhosted.org/packages/b0/b8/2c860c92b4134f68c7716c6f30a0d723973f881c32a6d7a24c4ddca05fdf/propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54", size = 281947 }, + { url = "https://files.pythonhosted.org/packages/cd/72/b564be7411b525d11757b713c757c21cd4dc13b6569c3b2b8f6d3c96fd5e/propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121", size = 268072 }, + { url = "https://files.pythonhosted.org/packages/37/68/d94649e399e8d7fc051e5a4f2334efc567993525af083db145a70690a121/propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e", size = 275190 }, + { url = "https://files.pythonhosted.org/packages/d8/3c/446e125f5bbbc1922964dd67cb541c01cdb678d811297b79a4ff6accc843/propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e", size = 254145 }, + { url = "https://files.pythonhosted.org/packages/f4/80/fd3f741483dc8e59f7ba7e05eaa0f4e11677d7db2077522b92ff80117a2a/propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a", size = 257163 }, + { url = "https://files.pythonhosted.org/packages/dc/cf/6292b5ce6ed0017e6a89024a827292122cc41b6259b30ada0c6732288513/propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac", size = 280249 }, + { url = "https://files.pythonhosted.org/packages/e8/f0/fd9b8247b449fe02a4f96538b979997e229af516d7462b006392badc59a1/propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e", size = 288741 }, + { url = "https://files.pythonhosted.org/packages/64/71/cf831fdc2617f86cfd7f414cfc487d018e722dac8acc098366ce9bba0941/propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf", size = 277061 }, + { url = "https://files.pythonhosted.org/packages/42/78/9432542a35d944abeca9e02927a0de38cd7a298466d8ffa171536e2381c3/propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863", size = 42252 }, + { url = "https://files.pythonhosted.org/packages/6f/45/960365f4f8978f48ebb56b1127adf33a49f2e69ecd46ac1f46d6cf78a79d/propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46", size = 46425 }, + { url = "https://files.pythonhosted.org/packages/6d/05/2695901870f8b8f5d68f7cbb05de92a7f21f032a0edc42a5b527d22eab28/propcache-0.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:03c091bb752349402f23ee43bb2bff6bd80ccab7c9df6b88ad4322258d6960fc", size = 80692 }, + { url = "https://files.pythonhosted.org/packages/57/5e/54d314533896ed43f5573ac80366a056f17a397234ada6e4303fa84a232f/propcache-0.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46ed02532cb66612d42ae5c3929b5e98ae330ea0f3900bc66ec5f4862069519b", size = 46434 }, + { url = "https://files.pythonhosted.org/packages/40/61/3624c088406e9e54beb42801e9da53cc8b379f4c1b4ee3911876282d4af6/propcache-0.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11ae6a8a01b8a4dc79093b5d3ca2c8a4436f5ee251a9840d7790dccbd96cb649", size = 45956 }, + { url = "https://files.pythonhosted.org/packages/e6/65/09b1bacf723721e36a84034ff0a4d64d13c7ddb92cfefe9c0b861886f814/propcache-0.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df03cd88f95b1b99052b52b1bb92173229d7a674df0ab06d2b25765ee8404bce", size = 208068 }, + { url = "https://files.pythonhosted.org/packages/57/7b/a6c8de8814f9f07b74c959e6d2ef1137ac2ff622fa1bd4cd00c5a6890525/propcache-0.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03acd9ff19021bd0567582ac88f821b66883e158274183b9e5586f678984f8fe", size = 223581 }, + { url = "https://files.pythonhosted.org/packages/fb/03/8c081bfb32bb0c12118aff9720c498015c332630858c9aaec7930c40911d/propcache-0.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd54895e4ae7d32f1e3dd91261df46ee7483a735017dc6f987904f194aa5fd14", size = 221567 }, + { url = "https://files.pythonhosted.org/packages/70/b8/a6dc434561bac3601644724635328e05ea6b9163e4a628f5f4222a384625/propcache-0.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a67e5c04e3119594d8cfae517f4b9330c395df07ea65eab16f3d559b7068fe", size = 208536 }, + { url = "https://files.pythonhosted.org/packages/1f/96/6f6fdb8bfd749803b160f23c446ef45f7cb51e355a24c5b07d8687ae2ee9/propcache-0.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee25f1ac091def37c4b59d192bbe3a206298feeb89132a470325bf76ad122a1e", size = 198920 }, + { url = "https://files.pythonhosted.org/packages/1b/6e/b407dff7f7dbbd9efd65236a53d4512929ce37026670af5c12f91bb95862/propcache-0.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58e6d2a5a7cb3e5f166fd58e71e9a4ff504be9dc61b88167e75f835da5764d07", size = 203802 }, + { url = "https://files.pythonhosted.org/packages/2f/77/2dc3a33bcbd3652686038267aff2a2ff03e71e9a7f76f444c72cadf1ba21/propcache-0.3.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:be90c94570840939fecedf99fa72839aed70b0ced449b415c85e01ae67422c90", size = 199682 }, + { url = "https://files.pythonhosted.org/packages/5f/49/bb38b9159cfd6c74a6daf368e644eecbbda05a2f4731b6d5b6446a7bcb34/propcache-0.3.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49ea05212a529c2caffe411e25a59308b07d6e10bf2505d77da72891f9a05641", size = 200815 }, + { url = "https://files.pythonhosted.org/packages/a3/d7/2d3cdf6e4fcc28bb3dd4cf23f6ae34cb24f2db4b7131a421bd7f38d70e56/propcache-0.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:119e244ab40f70a98c91906d4c1f4c5f2e68bd0b14e7ab0a06922038fae8a20f", size = 211553 }, + { url = "https://files.pythonhosted.org/packages/a7/64/efe070403dcb086d200a801dbf6e4d09f7f1278b15fae038038ad573eb22/propcache-0.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:507c5357a8d8b4593b97fb669c50598f4e6cccbbf77e22fa9598aba78292b4d7", size = 214878 }, + { url = "https://files.pythonhosted.org/packages/8f/ec/4ae54f9f8874c58ca1659a9dd260c3b312ca9911d3c74542ef003ca6e9b4/propcache-0.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8526b0941ec5a40220fc4dfde76aed58808e2b309c03e9fa8e2260083ef7157f", size = 207562 }, + { url = "https://files.pythonhosted.org/packages/d7/92/e07bd88ece413fd069d66533d95cbc83649b57b60990f26a35a7f84e25ed/propcache-0.3.0-cp39-cp39-win32.whl", hash = "sha256:7cedd25e5f678f7738da38037435b340694ab34d424938041aa630d8bac42663", size = 41152 }, + { url = "https://files.pythonhosted.org/packages/26/8f/676ea691f5788bd9376ba77475204093a559c883ee1b6def0291e41020dc/propcache-0.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf4298f366ca7e1ad1d21bbb58300a6985015909964077afd37559084590c929", size = 45263 }, + { url = "https://files.pythonhosted.org/packages/b5/35/6c4c6fc8774a9e3629cd750dc24a7a4fb090a25ccd5c3246d127b70f9e22/propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043", size = 12101 }, ] [[package]] @@ -2245,17 +2247,17 @@ wheels = [ [[package]] name = "psutil" -version = "6.1.1" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502 } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511 }, - { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985 }, - { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488 }, - { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477 }, - { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017 }, - { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602 }, - { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444 }, + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, ] [[package]] @@ -2453,15 +2455,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/06/0763e0ccc81754d3eadb21b2cb86cf21bdedc9b52698c2ad6785db7f0a4e/pypandoc-1.15-py3-none-any.whl", hash = "sha256:4ededcc76c8770f27aaca6dff47724578428eca84212a31479403a9731fc2b16", size = 21321 }, ] -[[package]] -name = "pyparsing" -version = "3.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/1a/3544f4f299a47911c2ab3710f534e52fea62a633c96806995da5d25be4b2/pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a", size = 1067694 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/a7/c8a2d361bf89c0d9577c934ebb7421b25dc84bf3a8e3ac0a40aed9acc547/pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1", size = 107716 }, -] - [[package]] name = "pyperclip" version = "1.9.0" @@ -2567,6 +2560,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0f/d7/03e0453719ed89724664f781f0255949408118093dbf77a2aa2a1198b38e/python_Levenshtein-0.26.1-py3-none-any.whl", hash = "sha256:8ef5e529dd640fb00f05ee62d998d2ee862f19566b641ace775d5ae16167b2ef", size = 9426 }, ] +[[package]] +name = "python-magic" +version = "0.4.27" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -2636,11 +2638,18 @@ dependencies = [ { name = "langchain-openai" }, { name = "langgraph" }, { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, { name = "litellm" }, + { name = "packaging" }, { name = "pathspec" }, + { name = "peewee" }, + { name = "peewee-migrate" }, + { name = "platformdirs" }, { name = "pyte" }, { name = "python-levenshtein" }, + { name = "python-magic" }, { name = "rapidfuzz" }, + { name = "requests" }, { name = "rich" }, { name = "tavily-python" }, { name = "uvicorn" }, @@ -2657,27 +2666,34 @@ dev = [ [package.metadata] requires-dist = [ - { name = "aider-chat", specifier = ">=0.72.0" }, + { name = "aider-chat", specifier = ">=0.75.1" }, { name = "fastapi", specifier = ">=0.104.0" }, { name = "fuzzywuzzy", specifier = "==0.18.0" }, { name = "gitpython", specifier = ">=3.1" }, { name = "jinja2", specifier = ">=3.1.2" }, - { name = "langchain", specifier = ">=0.3.18" }, - { name = "langchain-anthropic", specifier = ">=0.3.7" }, - { name = "langchain-core", specifier = ">=0.3.35" }, - { name = "langchain-google-genai", specifier = ">=2.0.9" }, - { name = "langchain-openai", specifier = ">=0.3.5" }, - { name = "langgraph", specifier = ">=0.2.71" }, - { name = "langgraph-checkpoint", specifier = ">=2.0.12" }, + { name = "langchain", specifier = ">=0.3.5" }, + { name = "langchain-anthropic", specifier = ">=0.3.9" }, + { name = "langchain-core", specifier = ">=0.3.5" }, + { name = "langchain-google-genai", specifier = ">=2.0.11" }, + { name = "langchain-openai", specifier = ">=0.3.8" }, + { name = "langgraph", specifier = ">=0.3.5" }, + { name = "langgraph-checkpoint", specifier = ">=2.0.18" }, + { name = "langgraph-prebuilt", specifier = ">=0.1.2" }, { name = "litellm", specifier = ">=1.60.6" }, + { name = "packaging" }, { name = "pathspec", specifier = ">=0.11.0" }, + { name = "peewee", specifier = ">=3.17.9" }, + { name = "peewee-migrate", specifier = ">=1.13.0" }, + { name = "platformdirs", specifier = ">=3.17.9" }, { name = "pyte", specifier = ">=0.8.2" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" }, { name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.14.0" }, { name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.2.0" }, { name = "python-levenshtein", specifier = ">=0.26.1" }, + { name = "python-magic", specifier = ">=0.4.27" }, { name = "rapidfuzz", specifier = ">=3.11.0" }, + { name = "requests" }, { name = "rich", specifier = ">=13.0.0" }, { name = "tavily-python", specifier = ">=0.5.0" }, { name = "uvicorn", specifier = ">=0.24.0" }, @@ -2922,112 +2938,112 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.22.3" +version = "0.23.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/80/cce854d0921ff2f0a9fa831ba3ad3c65cee3a46711addf39a2af52df2cfd/rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", size = 26771 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/79/2ce611b18c4fd83d9e3aecb5cba93e1917c050f556db39842889fa69b79f/rpds_py-0.23.1.tar.gz", hash = "sha256:7f3240dcfa14d198dba24b8b9cb3b108c06b68d45b7babd9eefc1038fdf7e707", size = 26806 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/2a/ead1d09e57449b99dcc190d8d2323e3a167421d8f8fdf0f217c6f6befe47/rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967", size = 359514 }, - { url = "https://files.pythonhosted.org/packages/8f/7e/1254f406b7793b586c68e217a6a24ec79040f85e030fff7e9049069284f4/rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37", size = 349031 }, - { url = "https://files.pythonhosted.org/packages/aa/da/17c6a2c73730d426df53675ff9cc6653ac7a60b6438d03c18e1c822a576a/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24", size = 381485 }, - { url = "https://files.pythonhosted.org/packages/aa/13/2dbacd820466aa2a3c4b747afb18d71209523d353cf865bf8f4796c969ea/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff", size = 386794 }, - { url = "https://files.pythonhosted.org/packages/6d/62/96905d0a35ad4e4bc3c098b2f34b2e7266e211d08635baa690643d2227be/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c", size = 423523 }, - { url = "https://files.pythonhosted.org/packages/eb/1b/d12770f2b6a9fc2c3ec0d810d7d440f6d465ccd8b7f16ae5385952c28b89/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e", size = 446695 }, - { url = "https://files.pythonhosted.org/packages/4d/cf/96f1fd75512a017f8e07408b6d5dbeb492d9ed46bfe0555544294f3681b3/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec", size = 381959 }, - { url = "https://files.pythonhosted.org/packages/ab/f0/d1c5b501c8aea85aeb938b555bfdf7612110a2f8cdc21ae0482c93dd0c24/rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c", size = 410420 }, - { url = "https://files.pythonhosted.org/packages/33/3b/45b6c58fb6aad5a569ae40fb890fc494c6b02203505a5008ee6dc68e65f7/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09", size = 557620 }, - { url = "https://files.pythonhosted.org/packages/83/62/3fdd2d3d47bf0bb9b931c4c73036b4ab3ec77b25e016ae26fab0f02be2af/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00", size = 584202 }, - { url = "https://files.pythonhosted.org/packages/04/f2/5dced98b64874b84ca824292f9cee2e3f30f3bcf231d15a903126684f74d/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf", size = 552787 }, - { url = "https://files.pythonhosted.org/packages/67/13/2273dea1204eda0aea0ef55145da96a9aa28b3f88bb5c70e994f69eda7c3/rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652", size = 220088 }, - { url = "https://files.pythonhosted.org/packages/4e/80/8c8176b67ad7f4a894967a7a4014ba039626d96f1d4874d53e409b58d69f/rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8", size = 231737 }, - { url = "https://files.pythonhosted.org/packages/15/ad/8d1ddf78f2805a71253fcd388017e7b4a0615c22c762b6d35301fef20106/rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f", size = 359773 }, - { url = "https://files.pythonhosted.org/packages/c8/75/68c15732293a8485d79fe4ebe9045525502a067865fa4278f178851b2d87/rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a", size = 349214 }, - { url = "https://files.pythonhosted.org/packages/3c/4c/7ce50f3070083c2e1b2bbd0fb7046f3da55f510d19e283222f8f33d7d5f4/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5", size = 380477 }, - { url = "https://files.pythonhosted.org/packages/9a/e9/835196a69cb229d5c31c13b8ae603bd2da9a6695f35fe4270d398e1db44c/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb", size = 386171 }, - { url = "https://files.pythonhosted.org/packages/f9/8e/33fc4eba6683db71e91e6d594a2cf3a8fbceb5316629f0477f7ece5e3f75/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2", size = 422676 }, - { url = "https://files.pythonhosted.org/packages/37/47/2e82d58f8046a98bb9497a8319604c92b827b94d558df30877c4b3c6ccb3/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0", size = 446152 }, - { url = "https://files.pythonhosted.org/packages/e1/78/79c128c3e71abbc8e9739ac27af11dc0f91840a86fce67ff83c65d1ba195/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1", size = 381300 }, - { url = "https://files.pythonhosted.org/packages/c9/5b/2e193be0e8b228c1207f31fa3ea79de64dadb4f6a4833111af8145a6bc33/rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d", size = 409636 }, - { url = "https://files.pythonhosted.org/packages/c2/3f/687c7100b762d62186a1c1100ffdf99825f6fa5ea94556844bbbd2d0f3a9/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648", size = 556708 }, - { url = "https://files.pythonhosted.org/packages/8c/a2/c00cbc4b857e8b3d5e7f7fc4c81e23afd8c138b930f4f3ccf9a41a23e9e4/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74", size = 583554 }, - { url = "https://files.pythonhosted.org/packages/d0/08/696c9872cf56effdad9ed617ac072f6774a898d46b8b8964eab39ec562d2/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a", size = 552105 }, - { url = "https://files.pythonhosted.org/packages/18/1f/4df560be1e994f5adf56cabd6c117e02de7c88ee238bb4ce03ed50da9d56/rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64", size = 220199 }, - { url = "https://files.pythonhosted.org/packages/b8/1b/c29b570bc5db8237553002788dc734d6bd71443a2ceac2a58202ec06ef12/rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c", size = 231775 }, - { url = "https://files.pythonhosted.org/packages/75/47/3383ee3bd787a2a5e65a9b9edc37ccf8505c0a00170e3a5e6ea5fbcd97f7/rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", size = 352334 }, - { url = "https://files.pythonhosted.org/packages/40/14/aa6400fa8158b90a5a250a77f2077c0d0cd8a76fce31d9f2b289f04c6dec/rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", size = 342111 }, - { url = "https://files.pythonhosted.org/packages/7d/06/395a13bfaa8a28b302fb433fb285a67ce0ea2004959a027aea8f9c52bad4/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", size = 384286 }, - { url = "https://files.pythonhosted.org/packages/43/52/d8eeaffab047e6b7b7ef7f00d5ead074a07973968ffa2d5820fa131d7852/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e", size = 391739 }, - { url = "https://files.pythonhosted.org/packages/83/31/52dc4bde85c60b63719610ed6f6d61877effdb5113a72007679b786377b8/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", size = 427306 }, - { url = "https://files.pythonhosted.org/packages/70/d5/1bab8e389c2261dba1764e9e793ed6830a63f830fdbec581a242c7c46bda/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", size = 442717 }, - { url = "https://files.pythonhosted.org/packages/82/a1/a45f3e30835b553379b3a56ea6c4eb622cf11e72008229af840e4596a8ea/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", size = 385721 }, - { url = "https://files.pythonhosted.org/packages/a6/27/780c942de3120bdd4d0e69583f9c96e179dfff082f6ecbb46b8d6488841f/rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", size = 415824 }, - { url = "https://files.pythonhosted.org/packages/94/0b/aa0542ca88ad20ea719b06520f925bae348ea5c1fdf201b7e7202d20871d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", size = 561227 }, - { url = "https://files.pythonhosted.org/packages/0d/92/3ed77d215f82c8f844d7f98929d56cc321bb0bcfaf8f166559b8ec56e5f1/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", size = 587424 }, - { url = "https://files.pythonhosted.org/packages/09/42/cacaeb047a22cab6241f107644f230e2935d4efecf6488859a7dd82fc47d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", size = 555953 }, - { url = "https://files.pythonhosted.org/packages/e6/52/c921dc6d5f5d45b212a456c1f5b17df1a471127e8037eb0972379e39dff4/rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", size = 221339 }, - { url = "https://files.pythonhosted.org/packages/f2/c7/f82b5be1e8456600395366f86104d1bd8d0faed3802ad511ef6d60c30d98/rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", size = 235786 }, - { url = "https://files.pythonhosted.org/packages/d0/bf/36d5cc1f2c609ae6e8bf0fc35949355ca9d8790eceb66e6385680c951e60/rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", size = 351657 }, - { url = "https://files.pythonhosted.org/packages/24/2a/f1e0fa124e300c26ea9382e59b2d582cba71cedd340f32d1447f4f29fa4e/rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", size = 341829 }, - { url = "https://files.pythonhosted.org/packages/cf/c2/0da1231dd16953845bed60d1a586fcd6b15ceaeb965f4d35cdc71f70f606/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", size = 384220 }, - { url = "https://files.pythonhosted.org/packages/c7/73/a4407f4e3a00a9d4b68c532bf2d873d6b562854a8eaff8faa6133b3588ec/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", size = 391009 }, - { url = "https://files.pythonhosted.org/packages/a9/c3/04b7353477ab360fe2563f5f0b176d2105982f97cd9ae80a9c5a18f1ae0f/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", size = 426989 }, - { url = "https://files.pythonhosted.org/packages/8d/e6/e4b85b722bcf11398e17d59c0f6049d19cd606d35363221951e6d625fcb0/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", size = 441544 }, - { url = "https://files.pythonhosted.org/packages/27/fc/403e65e56f65fff25f2973216974976d3f0a5c3f30e53758589b6dc9b79b/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", size = 385179 }, - { url = "https://files.pythonhosted.org/packages/57/9b/2be9ff9700d664d51fd96b33d6595791c496d2778cb0b2a634f048437a55/rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", size = 415103 }, - { url = "https://files.pythonhosted.org/packages/bb/a5/03c2ad8ca10994fcf22dd2150dd1d653bc974fa82d9a590494c84c10c641/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", size = 560916 }, - { url = "https://files.pythonhosted.org/packages/ba/2e/be4fdfc8b5b576e588782b56978c5b702c5a2307024120d8aeec1ab818f0/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", size = 587062 }, - { url = "https://files.pythonhosted.org/packages/67/e0/2034c221937709bf9c542603d25ad43a68b4b0a9a0c0b06a742f2756eb66/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", size = 555734 }, - { url = "https://files.pythonhosted.org/packages/ea/ce/240bae07b5401a22482b58e18cfbabaa392409b2797da60223cca10d7367/rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", size = 220663 }, - { url = "https://files.pythonhosted.org/packages/cb/f0/d330d08f51126330467edae2fa4efa5cec8923c87551a79299380fdea30d/rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", size = 235503 }, - { url = "https://files.pythonhosted.org/packages/f7/c4/dbe1cc03df013bf2feb5ad00615038050e7859f381e96fb5b7b4572cd814/rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", size = 347698 }, - { url = "https://files.pythonhosted.org/packages/a4/3a/684f66dd6b0f37499cad24cd1c0e523541fd768576fa5ce2d0a8799c3cba/rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", size = 337330 }, - { url = "https://files.pythonhosted.org/packages/82/eb/e022c08c2ce2e8f7683baa313476492c0e2c1ca97227fe8a75d9f0181e95/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", size = 380022 }, - { url = "https://files.pythonhosted.org/packages/e4/21/5a80e653e4c86aeb28eb4fea4add1f72e1787a3299687a9187105c3ee966/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", size = 390754 }, - { url = "https://files.pythonhosted.org/packages/37/a4/d320a04ae90f72d080b3d74597074e62be0a8ecad7d7321312dfe2dc5a6a/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", size = 423840 }, - { url = "https://files.pythonhosted.org/packages/87/70/674dc47d93db30a6624279284e5631be4c3a12a0340e8e4f349153546728/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", size = 438970 }, - { url = "https://files.pythonhosted.org/packages/3f/64/9500f4d66601d55cadd21e90784cfd5d5f4560e129d72e4339823129171c/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", size = 383146 }, - { url = "https://files.pythonhosted.org/packages/4d/45/630327addb1d17173adcf4af01336fd0ee030c04798027dfcb50106001e0/rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", size = 408294 }, - { url = "https://files.pythonhosted.org/packages/5f/ef/8efb3373cee54ea9d9980b772e5690a0c9e9214045a4e7fa35046e399fee/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", size = 556345 }, - { url = "https://files.pythonhosted.org/packages/54/01/151d3b9ef4925fc8f15bfb131086c12ec3c3d6dd4a4f7589c335bf8e85ba/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", size = 582292 }, - { url = "https://files.pythonhosted.org/packages/30/89/35fc7a6cdf3477d441c7aca5e9bbf5a14e0f25152aed7f63f4e0b141045d/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", size = 553855 }, - { url = "https://files.pythonhosted.org/packages/8f/e0/830c02b2457c4bd20a8c5bb394d31d81f57fbefce2dbdd2e31feff4f7003/rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", size = 219100 }, - { url = "https://files.pythonhosted.org/packages/f8/30/7ac943f69855c2db77407ae363484b915d861702dbba1aa82d68d57f42be/rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", size = 233794 }, - { url = "https://files.pythonhosted.org/packages/db/0f/a8ad17ddac7c880f48d5da50733dd25bfc35ba2be1bec9f23453e8c7a123/rpds_py-0.22.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea", size = 359735 }, - { url = "https://files.pythonhosted.org/packages/0c/41/430903669397ea3ee76865e0b53ea236e8dc0ffbecde47b2c4c783ad6759/rpds_py-0.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e", size = 348724 }, - { url = "https://files.pythonhosted.org/packages/c9/5c/3496f4f0ee818297544f2d5f641c49dde8ae156392e6834b79c0609ba006/rpds_py-0.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d", size = 381782 }, - { url = "https://files.pythonhosted.org/packages/b6/dc/db0523ce0cd16ce579185cc9aa9141992de956d0a9c469ecfd1fb5d54ddc/rpds_py-0.22.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3", size = 387036 }, - { url = "https://files.pythonhosted.org/packages/85/2a/9525c2427d2c257f877348918136a5d4e1b945c205a256e53bec61e54551/rpds_py-0.22.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091", size = 424566 }, - { url = "https://files.pythonhosted.org/packages/b9/1c/f8c012a39794b84069635709f559c0309103d5d74b3f5013916e6ca4f174/rpds_py-0.22.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e", size = 447203 }, - { url = "https://files.pythonhosted.org/packages/93/f5/c1c772364570d35b98ba64f36ec90c3c6d0b932bc4d8b9b4efef6dc64b07/rpds_py-0.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543", size = 382283 }, - { url = "https://files.pythonhosted.org/packages/10/06/f94f61313f94fc75c3c3aa74563f80bbd990e5b25a7c1a38cee7d5d0309b/rpds_py-0.22.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d", size = 410022 }, - { url = "https://files.pythonhosted.org/packages/3f/b0/37ab416a9528419920dfb64886c220f58fcbd66b978e0a91b66e9ee9a993/rpds_py-0.22.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99", size = 557817 }, - { url = "https://files.pythonhosted.org/packages/2c/5d/9daa18adcd676dd3b2817c8a7cec3f3ebeeb0ce0d05a1b63bf994fc5114f/rpds_py-0.22.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831", size = 585099 }, - { url = "https://files.pythonhosted.org/packages/41/3f/ad4e58035d3f848410aa3d59857b5f238bafab81c8b4a844281f80445d62/rpds_py-0.22.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520", size = 552818 }, - { url = "https://files.pythonhosted.org/packages/b8/19/123acae8f4cab3c9463097c3ced3cc87c46f405056e249c874940e045309/rpds_py-0.22.3-cp39-cp39-win32.whl", hash = "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9", size = 220246 }, - { url = "https://files.pythonhosted.org/packages/8b/8d/9db93e48d96ace1f6713c71ce72e2d94b71d82156c37b6a54e0930486f00/rpds_py-0.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c", size = 231932 }, - { url = "https://files.pythonhosted.org/packages/8b/63/e29f8ee14fcf383574f73b6bbdcbec0fbc2e5fc36b4de44d1ac389b1de62/rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d", size = 360786 }, - { url = "https://files.pythonhosted.org/packages/d3/e0/771ee28b02a24e81c8c0e645796a371350a2bb6672753144f36ae2d2afc9/rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd", size = 350589 }, - { url = "https://files.pythonhosted.org/packages/cf/49/abad4c4a1e6f3adf04785a99c247bfabe55ed868133e2d1881200aa5d381/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493", size = 381848 }, - { url = "https://files.pythonhosted.org/packages/3a/7d/f4bc6d6fbe6af7a0d2b5f2ee77079efef7c8528712745659ec0026888998/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96", size = 387879 }, - { url = "https://files.pythonhosted.org/packages/13/b0/575c797377fdcd26cedbb00a3324232e4cb2c5d121f6e4b0dbf8468b12ef/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123", size = 423916 }, - { url = "https://files.pythonhosted.org/packages/54/78/87157fa39d58f32a68d3326f8a81ad8fb99f49fe2aa7ad9a1b7d544f9478/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad", size = 448410 }, - { url = "https://files.pythonhosted.org/packages/59/69/860f89996065a88be1b6ff2d60e96a02b920a262d8aadab99e7903986597/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9", size = 382841 }, - { url = "https://files.pythonhosted.org/packages/bd/d7/bc144e10d27e3cb350f98df2492a319edd3caaf52ddfe1293f37a9afbfd7/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e", size = 409662 }, - { url = "https://files.pythonhosted.org/packages/14/2a/6bed0b05233c291a94c7e89bc76ffa1c619d4e1979fbfe5d96024020c1fb/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338", size = 558221 }, - { url = "https://files.pythonhosted.org/packages/11/23/cd8f566de444a137bc1ee5795e47069a947e60810ba4152886fe5308e1b7/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566", size = 583780 }, - { url = "https://files.pythonhosted.org/packages/8d/63/79c3602afd14d501f751e615a74a59040328da5ef29ed5754ae80d236b84/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe", size = 553619 }, - { url = "https://files.pythonhosted.org/packages/9f/2e/c5c1689e80298d4e94c75b70faada4c25445739d91b94c211244a3ed7ed1/rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d", size = 233338 }, - { url = "https://files.pythonhosted.org/packages/bc/b7/d2c205723e3b4d75b03215694f0297a1b4b395bf834cb5896ad9bbb90f90/rpds_py-0.22.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c", size = 360594 }, - { url = "https://files.pythonhosted.org/packages/d8/8f/c3515f5234cf6055046d4cfe9c80a3742a20acfa7d0b1b290f0d7f56a8db/rpds_py-0.22.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055", size = 349594 }, - { url = "https://files.pythonhosted.org/packages/6b/98/5b487cb06afc484befe350c87fda37f4ce11333f04f3380aba43dcf5bce2/rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723", size = 381138 }, - { url = "https://files.pythonhosted.org/packages/5e/3a/12308d2c51b3fdfc173619943b7dc5ba41b4850c47112eeda38d9c54ed12/rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728", size = 387828 }, - { url = "https://files.pythonhosted.org/packages/17/b2/c242241ab5a2a206e093f24ccbfa519c4bbf10a762ac90bffe1766c225e0/rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b", size = 424634 }, - { url = "https://files.pythonhosted.org/packages/d5/c7/52a1b15012139f3ba740f291f1d03c6b632938ba61bc605f24c101952493/rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d", size = 447862 }, - { url = "https://files.pythonhosted.org/packages/55/3e/4d3ed8fd01bad77e8ed101116fe63b03f1011940d9596a8f4d82ac80cacd/rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11", size = 382506 }, - { url = "https://files.pythonhosted.org/packages/30/78/df59d6f92470a84369a3757abeae1cfd7f7239c8beb6d948949bf78317d2/rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f", size = 410534 }, - { url = "https://files.pythonhosted.org/packages/38/97/ea45d1edd9b753b20084b52dd5db6ee5e1ac3e036a27149972398a413858/rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca", size = 557453 }, - { url = "https://files.pythonhosted.org/packages/08/cd/3a1b35eb9da27ffbb981cfffd32a01c7655c4431ccb278cb3064f8887462/rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3", size = 584412 }, - { url = "https://files.pythonhosted.org/packages/87/91/31d1c5aeb1606f71188259e0ba6ed6f5c21a3c72f58b51db6a8bd0aa2b5d/rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7", size = 553446 }, - { url = "https://files.pythonhosted.org/packages/e7/ad/03b5ccd1ab492c9dece85b3bf1c96453ab8c47983936fae6880f688f60b3/rpds_py-0.22.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6", size = 233013 }, + { url = "https://files.pythonhosted.org/packages/34/fe/e5326459863bd525122f4e9c80ac8d7c6cfa171b7518d04cc27c12c209b0/rpds_py-0.23.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2a54027554ce9b129fc3d633c92fa33b30de9f08bc61b32c053dc9b537266fed", size = 372123 }, + { url = "https://files.pythonhosted.org/packages/f9/db/f10a3795f7a89fb27594934012d21c61019bbeb516c5bdcfbbe9e9e617a7/rpds_py-0.23.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b5ef909a37e9738d146519657a1aab4584018746a18f71c692f2f22168ece40c", size = 356778 }, + { url = "https://files.pythonhosted.org/packages/21/27/0d3678ad7f432fa86f8fac5f5fc6496a4d2da85682a710d605219be20063/rpds_py-0.23.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ee9d6f0b38efb22ad94c3b68ffebe4c47865cdf4b17f6806d6c674e1feb4246", size = 385775 }, + { url = "https://files.pythonhosted.org/packages/99/a0/1786defa125b2ad228027f22dff26312ce7d1fee3c7c3c2682f403db2062/rpds_py-0.23.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7356a6da0562190558c4fcc14f0281db191cdf4cb96e7604c06acfcee96df15", size = 391181 }, + { url = "https://files.pythonhosted.org/packages/f1/5c/1240934050a7ffd020a915486d0cc4c7f6e7a2442a77aedf13664db55d36/rpds_py-0.23.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9441af1d25aed96901f97ad83d5c3e35e6cd21a25ca5e4916c82d7dd0490a4fa", size = 444607 }, + { url = "https://files.pythonhosted.org/packages/b7/1b/cee6905b47817fd0a377716dbe4df35295de46df46ee2ff704538cc371b0/rpds_py-0.23.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d8abf7896a91fb97e7977d1aadfcc2c80415d6dc2f1d0fca5b8d0df247248f3", size = 445550 }, + { url = "https://files.pythonhosted.org/packages/54/f7/f0821ca34032892d7a67fcd5042f50074ff2de64e771e10df01085c88d47/rpds_py-0.23.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b08027489ba8fedde72ddd233a5ea411b85a6ed78175f40285bd401bde7466d", size = 386148 }, + { url = "https://files.pythonhosted.org/packages/eb/ef/2afe53bc857c4bcba336acfd2629883a5746e7291023e017ac7fc98d85aa/rpds_py-0.23.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fee513135b5a58f3bb6d89e48326cd5aa308e4bcdf2f7d59f67c861ada482bf8", size = 416780 }, + { url = "https://files.pythonhosted.org/packages/ae/9a/38d2236cf669789b8a3e1a014c9b6a8d7b8925b952c92e7839ae2749f9ac/rpds_py-0.23.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:35d5631ce0af26318dba0ae0ac941c534453e42f569011585cb323b7774502a5", size = 558265 }, + { url = "https://files.pythonhosted.org/packages/e6/0a/f2705530c42578f20ed0b5b90135eecb30eef6e2ba73e7ba69087fad2dba/rpds_py-0.23.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a20cb698c4a59c534c6701b1c24a968ff2768b18ea2991f886bd8985ce17a89f", size = 585270 }, + { url = "https://files.pythonhosted.org/packages/29/4e/3b597dc84ed82c3d757ac9aa620de224a94e06d2e102069795ae7e81c015/rpds_py-0.23.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e9c206a1abc27e0588cf8b7c8246e51f1a16a103734f7750830a1ccb63f557a", size = 553850 }, + { url = "https://files.pythonhosted.org/packages/00/cc/6498b6f79e4375e6737247661e52a2d18f6accf4910e0c8da978674b4241/rpds_py-0.23.1-cp310-cp310-win32.whl", hash = "sha256:d9f75a06ecc68f159d5d7603b734e1ff6daa9497a929150f794013aa9f6e3f12", size = 220660 }, + { url = "https://files.pythonhosted.org/packages/17/2b/08db023d23e8c7032c99d8d2a70d32e450a868ab73d16e3ff5290308a665/rpds_py-0.23.1-cp310-cp310-win_amd64.whl", hash = "sha256:f35eff113ad430b5272bbfc18ba111c66ff525828f24898b4e146eb479a2cdda", size = 232551 }, + { url = "https://files.pythonhosted.org/packages/1c/67/6e5d4234bb9dee062ffca2a5f3c7cd38716317d6760ec235b175eed4de2c/rpds_py-0.23.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b79f5ced71efd70414a9a80bbbfaa7160da307723166f09b69773153bf17c590", size = 372264 }, + { url = "https://files.pythonhosted.org/packages/a7/0a/3dedb2daee8e783622427f5064e2d112751d8276ee73aa5409f000a132f4/rpds_py-0.23.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c9e799dac1ffbe7b10c1fd42fe4cd51371a549c6e108249bde9cd1200e8f59b4", size = 356883 }, + { url = "https://files.pythonhosted.org/packages/ed/fc/e1acef44f9c24b05fe5434b235f165a63a52959ac655e3f7a55726cee1a4/rpds_py-0.23.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721f9c4011b443b6e84505fc00cc7aadc9d1743f1c988e4c89353e19c4a968ee", size = 385624 }, + { url = "https://files.pythonhosted.org/packages/97/0a/a05951f6465d01622720c03ef6ef31adfbe865653e05ed7c45837492f25e/rpds_py-0.23.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f88626e3f5e57432e6191cd0c5d6d6b319b635e70b40be2ffba713053e5147dd", size = 391500 }, + { url = "https://files.pythonhosted.org/packages/ea/2e/cca0583ec0690ea441dceae23c0673b99755710ea22f40bccf1e78f41481/rpds_py-0.23.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:285019078537949cecd0190f3690a0b0125ff743d6a53dfeb7a4e6787af154f5", size = 444869 }, + { url = "https://files.pythonhosted.org/packages/cc/e6/95cda68b33a6d814d1e96b0e406d231ed16629101460d1740e92f03365e6/rpds_py-0.23.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b92f5654157de1379c509b15acec9d12ecf6e3bc1996571b6cb82a4302060447", size = 444930 }, + { url = "https://files.pythonhosted.org/packages/5f/a7/e94cdb73411ae9c11414d3c7c9a6ad75d22ad4a8d094fb45a345ba9e3018/rpds_py-0.23.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e768267cbe051dd8d1c5305ba690bb153204a09bf2e3de3ae530de955f5b5580", size = 386254 }, + { url = "https://files.pythonhosted.org/packages/dd/c5/a4a943d90a39e85efd1e04b1ad5129936786f9a9aa27bb7be8fc5d9d50c9/rpds_py-0.23.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c5334a71f7dc1160382d45997e29f2637c02f8a26af41073189d79b95d3321f1", size = 417090 }, + { url = "https://files.pythonhosted.org/packages/0c/a0/80d0013b12428d1fce0ab4e71829400b0a32caec12733c79e6109f843342/rpds_py-0.23.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6adb81564af0cd428910f83fa7da46ce9ad47c56c0b22b50872bc4515d91966", size = 557639 }, + { url = "https://files.pythonhosted.org/packages/a6/92/ec2e6980afb964a2cd7a99cbdef1f6c01116abe94b42cbe336ac93dd11c2/rpds_py-0.23.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cafa48f2133d4daa028473ede7d81cd1b9f9e6925e9e4003ebdf77010ee02f35", size = 584572 }, + { url = "https://files.pythonhosted.org/packages/3d/ce/75b6054db34a390789a82523790717b27c1bd735e453abb429a87c4f0f26/rpds_py-0.23.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fced9fd4a07a1ded1bac7e961ddd9753dd5d8b755ba8e05acba54a21f5f1522", size = 553028 }, + { url = "https://files.pythonhosted.org/packages/cc/24/f45abe0418c06a5cba0f846e967aa27bac765acd927aabd857c21319b8cc/rpds_py-0.23.1-cp311-cp311-win32.whl", hash = "sha256:243241c95174b5fb7204c04595852fe3943cc41f47aa14c3828bc18cd9d3b2d6", size = 220862 }, + { url = "https://files.pythonhosted.org/packages/2d/a6/3c0880e8bbfc36451ef30dc416266f6d2934705e468db5d21c8ba0ab6400/rpds_py-0.23.1-cp311-cp311-win_amd64.whl", hash = "sha256:11dd60b2ffddba85715d8a66bb39b95ddbe389ad2cfcf42c833f1bcde0878eaf", size = 232953 }, + { url = "https://files.pythonhosted.org/packages/f3/8c/d17efccb9f5b9137ddea706664aebae694384ae1d5997c0202093e37185a/rpds_py-0.23.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3902df19540e9af4cc0c3ae75974c65d2c156b9257e91f5101a51f99136d834c", size = 364369 }, + { url = "https://files.pythonhosted.org/packages/6e/c0/ab030f696b5c573107115a88d8d73d80f03309e60952b64c584c70c659af/rpds_py-0.23.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66f8d2a17e5838dd6fb9be6baaba8e75ae2f5fa6b6b755d597184bfcd3cb0eba", size = 349965 }, + { url = "https://files.pythonhosted.org/packages/b3/55/b40170f5a079c4fb0b6a82b299689e66e744edca3c3375a8b160fb797660/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:112b8774b0b4ee22368fec42749b94366bd9b536f8f74c3d4175d4395f5cbd31", size = 389064 }, + { url = "https://files.pythonhosted.org/packages/ab/1c/b03a912c59ec7c1e16b26e587b9dfa8ddff3b07851e781e8c46e908a365a/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0df046f2266e8586cf09d00588302a32923eb6386ced0ca5c9deade6af9a149", size = 397741 }, + { url = "https://files.pythonhosted.org/packages/52/6f/151b90792b62fb6f87099bcc9044c626881fdd54e31bf98541f830b15cea/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3288930b947cbebe767f84cf618d2cbe0b13be476e749da0e6a009f986248c", size = 448784 }, + { url = "https://files.pythonhosted.org/packages/71/2a/6de67c0c97ec7857e0e9e5cd7c52405af931b303eb1e5b9eff6c50fd9a2e/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce473a2351c018b06dd8d30d5da8ab5a0831056cc53b2006e2a8028172c37ce5", size = 440203 }, + { url = "https://files.pythonhosted.org/packages/db/5e/e759cd1c276d98a4b1f464b17a9bf66c65d29f8f85754e27e1467feaa7c3/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d550d7e9e7d8676b183b37d65b5cd8de13676a738973d330b59dc8312df9c5dc", size = 391611 }, + { url = "https://files.pythonhosted.org/packages/1c/1e/2900358efcc0d9408c7289769cba4c0974d9db314aa884028ed7f7364f61/rpds_py-0.23.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e14f86b871ea74c3fddc9a40e947d6a5d09def5adc2076ee61fb910a9014fb35", size = 423306 }, + { url = "https://files.pythonhosted.org/packages/23/07/6c177e6d059f5d39689352d6c69a926ee4805ffdb6f06203570234d3d8f7/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf5be5ba34e19be579ae873da515a2836a2166d8d7ee43be6ff909eda42b72b", size = 562323 }, + { url = "https://files.pythonhosted.org/packages/70/e4/f9097fd1c02b516fff9850792161eb9fc20a2fd54762f3c69eae0bdb67cb/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7031d493c4465dbc8d40bd6cafefef4bd472b17db0ab94c53e7909ee781b9ef", size = 588351 }, + { url = "https://files.pythonhosted.org/packages/87/39/5db3c6f326bfbe4576ae2af6435bd7555867d20ae690c786ff33659f293b/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55ff4151cfd4bc635e51cfb1c59ac9f7196b256b12e3a57deb9e5742e65941ad", size = 557252 }, + { url = "https://files.pythonhosted.org/packages/fd/14/2d5ad292f144fa79bafb78d2eb5b8a3a91c358b6065443cb9c49b5d1fedf/rpds_py-0.23.1-cp312-cp312-win32.whl", hash = "sha256:a9d3b728f5a5873d84cba997b9d617c6090ca5721caaa691f3b1a78c60adc057", size = 222181 }, + { url = "https://files.pythonhosted.org/packages/a3/4f/0fce63e0f5cdd658e71e21abd17ac1bc9312741ebb8b3f74eeed2ebdf771/rpds_py-0.23.1-cp312-cp312-win_amd64.whl", hash = "sha256:b03a8d50b137ee758e4c73638b10747b7c39988eb8e6cd11abb7084266455165", size = 237426 }, + { url = "https://files.pythonhosted.org/packages/13/9d/b8b2c0edffb0bed15be17b6d5ab06216f2f47f9ee49259c7e96a3ad4ca42/rpds_py-0.23.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4caafd1a22e5eaa3732acb7672a497123354bef79a9d7ceed43387d25025e935", size = 363672 }, + { url = "https://files.pythonhosted.org/packages/bd/c2/5056fa29e6894144d7ba4c938b9b0445f75836b87d2dd00ed4999dc45a8c/rpds_py-0.23.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:178f8a60fc24511c0eb756af741c476b87b610dba83270fce1e5a430204566a4", size = 349602 }, + { url = "https://files.pythonhosted.org/packages/b0/bc/33779a1bb0ee32d8d706b173825aab75c628521d23ce72a7c1e6a6852f86/rpds_py-0.23.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c632419c3870507ca20a37c8f8f5352317aca097639e524ad129f58c125c61c6", size = 388746 }, + { url = "https://files.pythonhosted.org/packages/62/0b/71db3e36b7780a619698ec82a9c87ab44ad7ca7f5480913e8a59ff76f050/rpds_py-0.23.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:698a79d295626ee292d1730bc2ef6e70a3ab135b1d79ada8fde3ed0047b65a10", size = 397076 }, + { url = "https://files.pythonhosted.org/packages/bb/2e/494398f613edf77ba10a916b1ddea2acce42ab0e3b62e2c70ffc0757ce00/rpds_py-0.23.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:271fa2184cf28bdded86bb6217c8e08d3a169fe0bbe9be5e8d96e8476b707122", size = 448399 }, + { url = "https://files.pythonhosted.org/packages/dd/53/4bd7f5779b1f463243ee5fdc83da04dd58a08f86e639dbffa7a35f969a84/rpds_py-0.23.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b91cceb5add79ee563bd1f70b30896bd63bc5f78a11c1f00a1e931729ca4f1f4", size = 439764 }, + { url = "https://files.pythonhosted.org/packages/f6/55/b3c18c04a460d951bf8e91f2abf46ce5b6426fb69784166a6a25827cb90a/rpds_py-0.23.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a6cb95074777f1ecda2ca4fa7717caa9ee6e534f42b7575a8f0d4cb0c24013", size = 390662 }, + { url = "https://files.pythonhosted.org/packages/2a/65/cc463044a3cbd616029b2aa87a651cdee8288d2fdd7780b2244845e934c1/rpds_py-0.23.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:50fb62f8d8364978478b12d5f03bf028c6bc2af04082479299139dc26edf4c64", size = 422680 }, + { url = "https://files.pythonhosted.org/packages/fa/8e/1fa52990c7836d72e8d70cd7753f2362c72fbb0a49c1462e8c60e7176d0b/rpds_py-0.23.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8f7e90b948dc9dcfff8003f1ea3af08b29c062f681c05fd798e36daa3f7e3e8", size = 561792 }, + { url = "https://files.pythonhosted.org/packages/57/b8/fe3b612979b1a29d0c77f8585903d8b3a292604b26d4b300e228b8ac6360/rpds_py-0.23.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5b98b6c953e5c2bda51ab4d5b4f172617d462eebc7f4bfdc7c7e6b423f6da957", size = 588127 }, + { url = "https://files.pythonhosted.org/packages/44/2d/fde474de516bbc4b9b230f43c98e7f8acc5da7fc50ceed8e7af27553d346/rpds_py-0.23.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2893d778d4671ee627bac4037a075168b2673c57186fb1a57e993465dbd79a93", size = 556981 }, + { url = "https://files.pythonhosted.org/packages/18/57/767deeb27b81370bbab8f74ef6e68d26c4ea99018f3c71a570e506fede85/rpds_py-0.23.1-cp313-cp313-win32.whl", hash = "sha256:2cfa07c346a7ad07019c33fb9a63cf3acb1f5363c33bc73014e20d9fe8b01cdd", size = 221936 }, + { url = "https://files.pythonhosted.org/packages/7d/6c/3474cfdd3cafe243f97ab8474ea8949236eb2a1a341ca55e75ce00cd03da/rpds_py-0.23.1-cp313-cp313-win_amd64.whl", hash = "sha256:3aaf141d39f45322e44fc2c742e4b8b4098ead5317e5f884770c8df0c332da70", size = 237145 }, + { url = "https://files.pythonhosted.org/packages/ec/77/e985064c624230f61efa0423759bb066da56ebe40c654f8b5ba225bd5d63/rpds_py-0.23.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:759462b2d0aa5a04be5b3e37fb8183615f47014ae6b116e17036b131985cb731", size = 359623 }, + { url = "https://files.pythonhosted.org/packages/62/d9/a33dcbf62b29e40559e012d525bae7d516757cf042cc9234bd34ca4b6aeb/rpds_py-0.23.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3e9212f52074fc9d72cf242a84063787ab8e21e0950d4d6709886fb62bcb91d5", size = 345900 }, + { url = "https://files.pythonhosted.org/packages/92/eb/f81a4be6397861adb2cb868bb6a28a33292c2dcac567d1dc575226055e55/rpds_py-0.23.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e9f3a3ac919406bc0414bbbd76c6af99253c507150191ea79fab42fdb35982a", size = 386426 }, + { url = "https://files.pythonhosted.org/packages/09/47/1f810c9b5e83be005341201b5389f1d240dfa440346ea7189f9b3fd6961d/rpds_py-0.23.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c04ca91dda8a61584165825907f5c967ca09e9c65fe8966ee753a3f2b019fe1e", size = 392314 }, + { url = "https://files.pythonhosted.org/packages/83/bd/bc95831432fd6c46ed8001f01af26de0763a059d6d7e6d69e3c5bf02917a/rpds_py-0.23.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab923167cfd945abb9b51a407407cf19f5bee35001221f2911dc85ffd35ff4f", size = 447706 }, + { url = "https://files.pythonhosted.org/packages/19/3e/567c04c226b1802dc6dc82cad3d53e1fa0a773258571c74ac5d8fbde97ed/rpds_py-0.23.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed6f011bedca8585787e5082cce081bac3d30f54520097b2411351b3574e1219", size = 437060 }, + { url = "https://files.pythonhosted.org/packages/fe/77/a77d2c6afe27ae7d0d55fc32f6841502648070dc8d549fcc1e6d47ff8975/rpds_py-0.23.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6959bb9928c5c999aba4a3f5a6799d571ddc2c59ff49917ecf55be2bbb4e3722", size = 389347 }, + { url = "https://files.pythonhosted.org/packages/3f/47/6b256ff20a74cfebeac790ab05586e0ac91f88e331125d4740a6c86fc26f/rpds_py-0.23.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ed7de3c86721b4e83ac440751329ec6a1102229aa18163f84c75b06b525ad7e", size = 415554 }, + { url = "https://files.pythonhosted.org/packages/fc/29/d4572469a245bc9fc81e35166dca19fc5298d5c43e1a6dd64bf145045193/rpds_py-0.23.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5fb89edee2fa237584e532fbf78f0ddd1e49a47c7c8cfa153ab4849dc72a35e6", size = 557418 }, + { url = "https://files.pythonhosted.org/packages/9c/0a/68cf7228895b1a3f6f39f51b15830e62456795e61193d2c8b87fd48c60db/rpds_py-0.23.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7e5413d2e2d86025e73f05510ad23dad5950ab8417b7fc6beaad99be8077138b", size = 583033 }, + { url = "https://files.pythonhosted.org/packages/14/18/017ab41dcd6649ad5db7d00155b4c212b31ab05bd857d5ba73a1617984eb/rpds_py-0.23.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d31ed4987d72aabdf521eddfb6a72988703c091cfc0064330b9e5f8d6a042ff5", size = 554880 }, + { url = "https://files.pythonhosted.org/packages/2e/dd/17de89431268da8819d8d51ce67beac28d9b22fccf437bc5d6d2bcd1acdb/rpds_py-0.23.1-cp313-cp313t-win32.whl", hash = "sha256:f3429fb8e15b20961efca8c8b21432623d85db2228cc73fe22756c6637aa39e7", size = 219743 }, + { url = "https://files.pythonhosted.org/packages/68/15/6d22d07e063ce5e9bfbd96db9ec2fbb4693591b4503e3a76996639474d02/rpds_py-0.23.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d6f6512a90bd5cd9030a6237f5346f046c6f0e40af98657568fa45695d4de59d", size = 235415 }, + { url = "https://files.pythonhosted.org/packages/f8/a1/d3fb6a8de191f09fb88eacd1505ae1cab6ffc1c2b57ef62db6632e9b6216/rpds_py-0.23.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:09cd7dbcb673eb60518231e02874df66ec1296c01a4fcd733875755c02014b19", size = 372686 }, + { url = "https://files.pythonhosted.org/packages/b5/3a/bb96c8164aadfb2c9d7290e553e78e9816fcf3e22dcddc98bc1b83974c8e/rpds_py-0.23.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c6760211eee3a76316cf328f5a8bd695b47b1626d21c8a27fb3b2473a884d597", size = 357098 }, + { url = "https://files.pythonhosted.org/packages/30/21/3de5d944f630a9fa6acf68191652e34e708041085770d426635c04dd60e3/rpds_py-0.23.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72e680c1518733b73c994361e4b06441b92e973ef7d9449feec72e8ee4f713da", size = 386304 }, + { url = "https://files.pythonhosted.org/packages/f1/a5/d554cd53e865a45e41bea61b3ff91a12e50b7422f4a273d980c02a261b42/rpds_py-0.23.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae28144c1daa61366205d32abd8c90372790ff79fc60c1a8ad7fd3c8553a600e", size = 391585 }, + { url = "https://files.pythonhosted.org/packages/72/5a/c53b507def60692e8c32fbafaa7ceb3cac81c5ab80f876ae6c8426be147d/rpds_py-0.23.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c698d123ce5d8f2d0cd17f73336615f6a2e3bdcedac07a1291bb4d8e7d82a05a", size = 445275 }, + { url = "https://files.pythonhosted.org/packages/05/15/1d68c0ad769a4bfb6cd2d1bff71bd2f4cbdf277d9b86c97f66f6fd107611/rpds_py-0.23.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98b257ae1e83f81fb947a363a274c4eb66640212516becaff7bef09a5dceacaa", size = 445722 }, + { url = "https://files.pythonhosted.org/packages/5a/4b/21fabed47908f85084b845bd49cd9706071a8ec970cdfe72aca8364c9369/rpds_py-0.23.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9ff044eb07c8468594d12602291c635da292308c8c619244e30698e7fc455a", size = 386605 }, + { url = "https://files.pythonhosted.org/packages/bd/fe/6d949043b7daad8b730436fcd8524231653e6cd95d55b806666f7ef62b64/rpds_py-0.23.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7938c7b0599a05246d704b3f5e01be91a93b411d0d6cc62275f025293b8a11ce", size = 417475 }, + { url = "https://files.pythonhosted.org/packages/4d/24/082e670e7d18cee2be04bbfa881e30b4c9ce1c139769d6ea0a8fd4aefdd0/rpds_py-0.23.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e9cb79ecedfc156c0692257ac7ed415243b6c35dd969baa461a6888fc79f2f07", size = 559004 }, + { url = "https://files.pythonhosted.org/packages/51/48/ef27d68d569c3bde34f8be76352a391619d1fd2fc40f7cb8972b8fc5e54b/rpds_py-0.23.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7b77e07233925bd33fc0022b8537774423e4c6680b6436316c5075e79b6384f4", size = 585260 }, + { url = "https://files.pythonhosted.org/packages/1b/6c/9599de109f16033f499542ba5792d6ebf2df0fd23124bd522351860c5c03/rpds_py-0.23.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a970bfaf130c29a679b1d0a6e0f867483cea455ab1535fb427566a475078f27f", size = 555057 }, + { url = "https://files.pythonhosted.org/packages/cc/34/1dadb0b9ecbc7ce72f1d8666b70c5cd4891ff9af03d7e59f80018421ca3c/rpds_py-0.23.1-cp39-cp39-win32.whl", hash = "sha256:4233df01a250b3984465faed12ad472f035b7cd5240ea3f7c76b7a7016084495", size = 220756 }, + { url = "https://files.pythonhosted.org/packages/b3/cb/a01607dc98b438245a2fff09981fe2814234c0722d5ea22ddfa8eb5802ba/rpds_py-0.23.1-cp39-cp39-win_amd64.whl", hash = "sha256:c617d7453a80e29d9973b926983b1e700a9377dbe021faa36041c78537d7b08c", size = 232693 }, + { url = "https://files.pythonhosted.org/packages/95/a9/6fafd35fc6bac05f59bcbc800b57cef877911ff1c015397c519fec888642/rpds_py-0.23.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c1f8afa346ccd59e4e5630d5abb67aba6a9812fddf764fd7eb11f382a345f8cc", size = 373463 }, + { url = "https://files.pythonhosted.org/packages/5b/ac/44f00029b8fbe0903a19e9a87a9b86063bf8700df2cc58868373d378418c/rpds_py-0.23.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fad784a31869747df4ac968a351e070c06ca377549e4ace94775aaa3ab33ee06", size = 358400 }, + { url = "https://files.pythonhosted.org/packages/5e/9c/3da199346c68d785f10dccab123b74c8c5f73be3f742c9e33d1116e07931/rpds_py-0.23.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5a96fcac2f18e5a0a23a75cd27ce2656c66c11c127b0318e508aab436b77428", size = 386815 }, + { url = "https://files.pythonhosted.org/packages/d3/45/8f6533c33c0d33da8c2c8b2fb8f2ee90b23c05c679b86b0ac6aee4653749/rpds_py-0.23.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e77febf227a1dc3220159355dba68faa13f8dca9335d97504abf428469fb18b", size = 392974 }, + { url = "https://files.pythonhosted.org/packages/ca/56/6a9ac1bf0455ba07385d8fe98c571c519b4f2000cff6581487bf9fab9272/rpds_py-0.23.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26bb3e8de93443d55e2e748e9fd87deb5f8075ca7bc0502cfc8be8687d69a2ec", size = 446019 }, + { url = "https://files.pythonhosted.org/packages/f4/83/5d9a3f9731cdccf49088bcc4ce821a5cf50bd1737cdad83e9959a7b9054d/rpds_py-0.23.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db7707dde9143a67b8812c7e66aeb2d843fe33cc8e374170f4d2c50bd8f2472d", size = 445811 }, + { url = "https://files.pythonhosted.org/packages/44/50/f2e0a98c62fc1fe68b176caca587714dc5c8bb2c3d1dd1eeb2bd4cc787ac/rpds_py-0.23.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eedaaccc9bb66581d4ae7c50e15856e335e57ef2734dbc5fd8ba3e2a4ab3cb6", size = 388070 }, + { url = "https://files.pythonhosted.org/packages/f2/d0/4981878f8f157e6dbea01d95e0119bf3d6b4c2c884fe64a9e6987f941104/rpds_py-0.23.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28358c54fffadf0ae893f6c1050e8f8853e45df22483b7fff2f6ab6152f5d8bf", size = 419173 }, + { url = "https://files.pythonhosted.org/packages/ce/13/fc971c470da96b270d2f64fedee987351bd935dc3016932a5cdcb1a88a2a/rpds_py-0.23.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:633462ef7e61d839171bf206551d5ab42b30b71cac8f10a64a662536e057fdef", size = 559048 }, + { url = "https://files.pythonhosted.org/packages/42/02/be91e1de139ec8b4f9fec4192fd779ba48af281cfc762c0ca4c15b945484/rpds_py-0.23.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a98f510d86f689fcb486dc59e6e363af04151e5260ad1bdddb5625c10f1e95f8", size = 584773 }, + { url = "https://files.pythonhosted.org/packages/27/28/3af8a1956df3edc41d884267d766dc096496dafc83f02f764a475eca0b4a/rpds_py-0.23.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e0397dd0b3955c61ef9b22838144aa4bef6f0796ba5cc8edfc64d468b93798b4", size = 555153 }, + { url = "https://files.pythonhosted.org/packages/5e/bb/e45f51c4e1327dea3c72b846c6de129eebacb7a6cb309af7af35d0578c80/rpds_py-0.23.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:75307599f0d25bf6937248e5ac4e3bde5ea72ae6618623b86146ccc7845ed00b", size = 233827 }, + { url = "https://files.pythonhosted.org/packages/b8/b5/7bf30fe885b6a6610a0ba984d40b7b70e1965ed9534a9fdeb53b12831dec/rpds_py-0.23.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3614d280bf7aab0d3721b5ce0e73434acb90a2c993121b6e81a1c15c665298ac", size = 373265 }, + { url = "https://files.pythonhosted.org/packages/40/b9/bdd81417fcaca7e0b204c38adfdf6de1c2662fdec447990081ff4eb204e8/rpds_py-0.23.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e5963ea87f88bddf7edd59644a35a0feecf75f8985430124c253612d4f7d27ae", size = 358335 }, + { url = "https://files.pythonhosted.org/packages/75/cc/0878cf297fb06a031f0127dce5e692c5a89f1cdb0554187049bf2a4fc214/rpds_py-0.23.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad76f44f70aac3a54ceb1813ca630c53415da3a24fd93c570b2dfb4856591017", size = 386969 }, + { url = "https://files.pythonhosted.org/packages/db/51/3be68a7e632d5bb4bfa539b0c0c6d590c1caea358d51331926d7b3102e2f/rpds_py-0.23.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c6ae11e6e93728d86aafc51ced98b1658a0080a7dd9417d24bfb955bb09c3c2", size = 393101 }, + { url = "https://files.pythonhosted.org/packages/a9/30/3abe08087d86a9a8e23b5ebe2055de301a54542c7572a91e7af891626849/rpds_py-0.23.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc869af5cba24d45fb0399b0cfdbcefcf6910bf4dee5d74036a57cf5264b3ff4", size = 446061 }, + { url = "https://files.pythonhosted.org/packages/08/a0/b6b0b100f8b7872d5f18b27d24687b61559d791491434d0976c986bb8c88/rpds_py-0.23.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76b32eb2ab650a29e423525e84eb197c45504b1c1e6e17b6cc91fcfeb1a4b1d", size = 445614 }, + { url = "https://files.pythonhosted.org/packages/d2/e4/451efd1fd8ffb9ae0b08cc2390ad7a1d2bb7049a78bd851c90ceb18fc265/rpds_py-0.23.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4263320ed887ed843f85beba67f8b2d1483b5947f2dc73a8b068924558bfeace", size = 387943 }, + { url = "https://files.pythonhosted.org/packages/8b/8e/805a4e6df48419cfa4433e8d4ec9596c02036bbc1b0d4a943aff828dd0cf/rpds_py-0.23.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7f9682a8f71acdf59fd554b82b1c12f517118ee72c0f3944eda461606dfe7eb9", size = 418552 }, + { url = "https://files.pythonhosted.org/packages/11/2e/807df78a7de1fc16d31f9c48d8620d99356a69728f6d5625f48e7183cd5c/rpds_py-0.23.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:754fba3084b70162a6b91efceee8a3f06b19e43dac3f71841662053c0584209a", size = 559194 }, + { url = "https://files.pythonhosted.org/packages/3f/46/d5ba680221182cac547053f824f1aa99f4011b9429d526992e742926ea5a/rpds_py-0.23.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:a1c66e71ecfd2a4acf0e4bd75e7a3605afa8f9b28a3b497e4ba962719df2be57", size = 585645 }, + { url = "https://files.pythonhosted.org/packages/46/14/905045ee7234ebf8c0362862b89376a5708709ad748d20bc5bb68b111407/rpds_py-0.23.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8d67beb6002441faef8251c45e24994de32c4c8686f7356a1f601ad7c466f7c3", size = 554521 }, + { url = "https://files.pythonhosted.org/packages/22/40/67897b5b04d2741e256b0010bd825c9e6a30562f99fd6def038e8c0d0a97/rpds_py-0.23.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a1e17d8dc8e57d8e0fd21f8f0f0a5211b3fa258b2e444c2053471ef93fe25a00", size = 233543 }, ] [[package]] @@ -3104,6 +3120,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763 }, +] + [[package]] name = "sounddevice" version = "0.5.1" @@ -3238,44 +3263,44 @@ wheels = [ [[package]] name = "tiktoken" -version = "0.8.0" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/02/576ff3a6639e755c4f70997b2d315f56d6d71e0d046f4fb64cb81a3fb099/tiktoken-0.8.0.tar.gz", hash = "sha256:9ccbb2740f24542534369c5635cfd9b2b3c2490754a78ac8831d99f89f94eeb2", size = 35107 } +sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/ba/a35fad753bbca8ba0cc1b0f3402a70256a110ced7ac332cf84ba89fc87ab/tiktoken-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b07e33283463089c81ef1467180e3e00ab00d46c2c4bbcef0acab5f771d6695e", size = 1039905 }, - { url = "https://files.pythonhosted.org/packages/91/05/13dab8fd7460391c387b3e69e14bf1e51ff71fe0a202cd2933cc3ea93fb6/tiktoken-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9269348cb650726f44dd3bbb3f9110ac19a8dcc8f54949ad3ef652ca22a38e21", size = 982417 }, - { url = "https://files.pythonhosted.org/packages/e9/98/18ec4a8351a6cf4537e40cd6e19a422c10cce1ef00a2fcb716e0a96af58b/tiktoken-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e13f37bc4ef2d012731e93e0fef21dc3b7aea5bb9009618de9a4026844e560", size = 1144915 }, - { url = "https://files.pythonhosted.org/packages/2e/28/cf3633018cbcc6deb7805b700ccd6085c9a5a7f72b38974ee0bffd56d311/tiktoken-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f13d13c981511331eac0d01a59b5df7c0d4060a8be1e378672822213da51e0a2", size = 1177221 }, - { url = "https://files.pythonhosted.org/packages/57/81/8a5be305cbd39d4e83a794f9e80c7f2c84b524587b7feb27c797b2046d51/tiktoken-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6b2ddbc79a22621ce8b1166afa9f9a888a664a579350dc7c09346a3b5de837d9", size = 1237398 }, - { url = "https://files.pythonhosted.org/packages/dc/da/8d1cc3089a83f5cf11c2e489332752981435280285231924557350523a59/tiktoken-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d8c2d0e5ba6453a290b86cd65fc51fedf247e1ba170191715b049dac1f628005", size = 884215 }, - { url = "https://files.pythonhosted.org/packages/f6/1e/ca48e7bfeeccaf76f3a501bd84db1fa28b3c22c9d1a1f41af9fb7579c5f6/tiktoken-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d622d8011e6d6f239297efa42a2657043aaed06c4f68833550cac9e9bc723ef1", size = 1039700 }, - { url = "https://files.pythonhosted.org/packages/8c/f8/f0101d98d661b34534769c3818f5af631e59c36ac6d07268fbfc89e539ce/tiktoken-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2efaf6199717b4485031b4d6edb94075e4d79177a172f38dd934d911b588d54a", size = 982413 }, - { url = "https://files.pythonhosted.org/packages/ac/3c/2b95391d9bd520a73830469f80a96e3790e6c0a5ac2444f80f20b4b31051/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5637e425ce1fc49cf716d88df3092048359a4b3bbb7da762840426e937ada06d", size = 1144242 }, - { url = "https://files.pythonhosted.org/packages/01/c4/c4a4360de845217b6aa9709c15773484b50479f36bb50419c443204e5de9/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb0e352d1dbe15aba082883058b3cce9e48d33101bdaac1eccf66424feb5b47", size = 1176588 }, - { url = "https://files.pythonhosted.org/packages/f8/a3/ef984e976822cd6c2227c854f74d2e60cf4cd6fbfca46251199914746f78/tiktoken-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56edfefe896c8f10aba372ab5706b9e3558e78db39dd497c940b47bf228bc419", size = 1237261 }, - { url = "https://files.pythonhosted.org/packages/1e/86/eea2309dc258fb86c7d9b10db536434fc16420feaa3b6113df18b23db7c2/tiktoken-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:326624128590def898775b722ccc327e90b073714227175ea8febbc920ac0a99", size = 884537 }, - { url = "https://files.pythonhosted.org/packages/c1/22/34b2e136a6f4af186b6640cbfd6f93400783c9ef6cd550d9eab80628d9de/tiktoken-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:881839cfeae051b3628d9823b2e56b5cc93a9e2efb435f4cf15f17dc45f21586", size = 1039357 }, - { url = "https://files.pythonhosted.org/packages/04/d2/c793cf49c20f5855fd6ce05d080c0537d7418f22c58e71f392d5e8c8dbf7/tiktoken-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe9399bdc3f29d428f16a2f86c3c8ec20be3eac5f53693ce4980371c3245729b", size = 982616 }, - { url = "https://files.pythonhosted.org/packages/b3/a1/79846e5ef911cd5d75c844de3fa496a10c91b4b5f550aad695c5df153d72/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a58deb7075d5b69237a3ff4bb51a726670419db6ea62bdcd8bd80c78497d7ab", size = 1144011 }, - { url = "https://files.pythonhosted.org/packages/26/32/e0e3a859136e95c85a572e4806dc58bf1ddf651108ae8b97d5f3ebe1a244/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2908c0d043a7d03ebd80347266b0e58440bdef5564f84f4d29fb235b5df3b04", size = 1175432 }, - { url = "https://files.pythonhosted.org/packages/c7/89/926b66e9025b97e9fbabeaa59048a736fe3c3e4530a204109571104f921c/tiktoken-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:294440d21a2a51e12d4238e68a5972095534fe9878be57d905c476017bff99fc", size = 1236576 }, - { url = "https://files.pythonhosted.org/packages/45/e2/39d4aa02a52bba73b2cd21ba4533c84425ff8786cc63c511d68c8897376e/tiktoken-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d8f3192733ac4d77977432947d563d7e1b310b96497acd3c196c9bddb36ed9db", size = 883824 }, - { url = "https://files.pythonhosted.org/packages/e3/38/802e79ba0ee5fcbf240cd624143f57744e5d411d2e9d9ad2db70d8395986/tiktoken-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:02be1666096aff7da6cbd7cdaa8e7917bfed3467cd64b38b1f112e96d3b06a24", size = 1039648 }, - { url = "https://files.pythonhosted.org/packages/b1/da/24cdbfc302c98663fbea66f5866f7fa1048405c7564ab88483aea97c3b1a/tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94ff53c5c74b535b2cbf431d907fc13c678bbd009ee633a2aca269a04389f9a", size = 982763 }, - { url = "https://files.pythonhosted.org/packages/e4/f0/0ecf79a279dfa41fc97d00adccf976ecc2556d3c08ef3e25e45eb31f665b/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b231f5e8982c245ee3065cd84a4712d64692348bc609d84467c57b4b72dcbc5", size = 1144417 }, - { url = "https://files.pythonhosted.org/packages/ab/d3/155d2d4514f3471a25dc1d6d20549ef254e2aa9bb5b1060809b1d3b03d3a/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4177faa809bd55f699e88c96d9bb4635d22e3f59d635ba6fd9ffedf7150b9953", size = 1175108 }, - { url = "https://files.pythonhosted.org/packages/19/eb/5989e16821ee8300ef8ee13c16effc20dfc26c777d05fbb6825e3c037b81/tiktoken-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5376b6f8dc4753cd81ead935c5f518fa0fbe7e133d9e25f648d8c4dabdd4bad7", size = 1236520 }, - { url = "https://files.pythonhosted.org/packages/40/59/14b20465f1d1cb89cfbc96ec27e5617b2d41c79da12b5e04e96d689be2a7/tiktoken-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:18228d624807d66c87acd8f25fc135665617cab220671eb65b50f5d70fa51f69", size = 883849 }, - { url = "https://files.pythonhosted.org/packages/08/f3/8a8ba9329e6b426d822c974d58fc6477f3f7b3b8deef651813d275cbe75f/tiktoken-0.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17807445f0cf1f25771c9d86496bd8b5c376f7419912519699f3cc4dc5c12e", size = 1040915 }, - { url = "https://files.pythonhosted.org/packages/42/7a/914bd98100449422778f9222d00b3a4ee654211c40784e57541fa46311ab/tiktoken-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:886f80bd339578bbdba6ed6d0567a0d5c6cfe198d9e587ba6c447654c65b8edc", size = 983753 }, - { url = "https://files.pythonhosted.org/packages/f7/01/1483856d84827c5fe541cb160f07914c6b063b8d961146e9c3557c4730c0/tiktoken-0.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6adc8323016d7758d6de7313527f755b0fc6c72985b7d9291be5d96d73ecd1e1", size = 1145913 }, - { url = "https://files.pythonhosted.org/packages/c2/e1/6c7a772e0200131e960e3381f1d7b26406bc5612c70677989c1498af2a60/tiktoken-0.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b591fb2b30d6a72121a80be24ec7a0e9eb51c5500ddc7e4c2496516dd5e3816b", size = 1178505 }, - { url = "https://files.pythonhosted.org/packages/3e/6b/3ae00f0bff5d0b6925bf6370cf0ff606f56daed76210c2b0a156017b78dc/tiktoken-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:845287b9798e476b4d762c3ebda5102be87ca26e5d2c9854002825d60cdb815d", size = 1239111 }, - { url = "https://files.pythonhosted.org/packages/d5/3b/7c8812952ca55e1bab08afc1dda3c5991804c71b550b9402e82a082ab795/tiktoken-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:1473cfe584252dc3fa62adceb5b1c763c1874e04511b197da4e6de51d6ce5a02", size = 884803 }, + { url = "https://files.pythonhosted.org/packages/64/f3/50ec5709fad61641e4411eb1b9ac55b99801d71f1993c29853f256c726c9/tiktoken-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:586c16358138b96ea804c034b8acf3f5d3f0258bd2bc3b0227af4af5d622e382", size = 1065770 }, + { url = "https://files.pythonhosted.org/packages/d6/f8/5a9560a422cf1755b6e0a9a436e14090eeb878d8ec0f80e0cd3d45b78bf4/tiktoken-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9c59ccc528c6c5dd51820b3474402f69d9a9e1d656226848ad68a8d5b2e5108", size = 1009314 }, + { url = "https://files.pythonhosted.org/packages/bc/20/3ed4cfff8f809cb902900ae686069e029db74567ee10d017cb254df1d598/tiktoken-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0968d5beeafbca2a72c595e8385a1a1f8af58feaebb02b227229b69ca5357fd", size = 1143140 }, + { url = "https://files.pythonhosted.org/packages/f1/95/cc2c6d79df8f113bdc6c99cdec985a878768120d87d839a34da4bd3ff90a/tiktoken-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a5fb085a6a3b7350b8fc838baf493317ca0e17bd95e8642f95fc69ecfed1de", size = 1197860 }, + { url = "https://files.pythonhosted.org/packages/c7/6c/9c1a4cc51573e8867c9381db1814223c09ebb4716779c7f845d48688b9c8/tiktoken-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15a2752dea63d93b0332fb0ddb05dd909371ededa145fe6a3242f46724fa7990", size = 1259661 }, + { url = "https://files.pythonhosted.org/packages/cd/4c/22eb8e9856a2b1808d0a002d171e534eac03f96dbe1161978d7389a59498/tiktoken-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:26113fec3bd7a352e4b33dbaf1bd8948de2507e30bd95a44e2b1156647bc01b4", size = 894026 }, + { url = "https://files.pythonhosted.org/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987 }, + { url = "https://files.pythonhosted.org/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155 }, + { url = "https://files.pythonhosted.org/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898 }, + { url = "https://files.pythonhosted.org/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535 }, + { url = "https://files.pythonhosted.org/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548 }, + { url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895 }, + { url = "https://files.pythonhosted.org/packages/cf/e5/21ff33ecfa2101c1bb0f9b6df750553bd873b7fb532ce2cb276ff40b197f/tiktoken-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e88f121c1c22b726649ce67c089b90ddda8b9662545a8aeb03cfef15967ddd03", size = 1065073 }, + { url = "https://files.pythonhosted.org/packages/8e/03/a95e7b4863ee9ceec1c55983e4cc9558bcfd8f4f80e19c4f8a99642f697d/tiktoken-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6600660f2f72369acb13a57fb3e212434ed38b045fd8cc6cdd74947b4b5d210", size = 1008075 }, + { url = "https://files.pythonhosted.org/packages/40/10/1305bb02a561595088235a513ec73e50b32e74364fef4de519da69bc8010/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e811743b5dfa74f4b227927ed86cbc57cad4df859cb3b643be797914e41794", size = 1140754 }, + { url = "https://files.pythonhosted.org/packages/1b/40/da42522018ca496432ffd02793c3a72a739ac04c3794a4914570c9bb2925/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99376e1370d59bcf6935c933cb9ba64adc29033b7e73f5f7569f3aad86552b22", size = 1196678 }, + { url = "https://files.pythonhosted.org/packages/5c/41/1e59dddaae270ba20187ceb8aa52c75b24ffc09f547233991d5fd822838b/tiktoken-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:badb947c32739fb6ddde173e14885fb3de4d32ab9d8c591cbd013c22b4c31dd2", size = 1259283 }, + { url = "https://files.pythonhosted.org/packages/5b/64/b16003419a1d7728d0d8c0d56a4c24325e7b10a21a9dd1fc0f7115c02f0a/tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16", size = 894897 }, + { url = "https://files.pythonhosted.org/packages/7a/11/09d936d37f49f4f494ffe660af44acd2d99eb2429d60a57c71318af214e0/tiktoken-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b0e8e05a26eda1249e824156d537015480af7ae222ccb798e5234ae0285dbdb", size = 1064919 }, + { url = "https://files.pythonhosted.org/packages/80/0e/f38ba35713edb8d4197ae602e80837d574244ced7fb1b6070b31c29816e0/tiktoken-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27d457f096f87685195eea0165a1807fae87b97b2161fe8c9b1df5bd74ca6f63", size = 1007877 }, + { url = "https://files.pythonhosted.org/packages/fe/82/9197f77421e2a01373e27a79dd36efdd99e6b4115746ecc553318ecafbf0/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf8ded49cddf825390e36dd1ad35cd49589e8161fdcb52aa25f0583e90a3e01", size = 1140095 }, + { url = "https://files.pythonhosted.org/packages/f2/bb/4513da71cac187383541facd0291c4572b03ec23c561de5811781bbd988f/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc156cb314119a8bb9748257a2eaebd5cc0753b6cb491d26694ed42fc7cb3139", size = 1195649 }, + { url = "https://files.pythonhosted.org/packages/fa/5c/74e4c137530dd8504e97e3a41729b1103a4ac29036cbfd3250b11fd29451/tiktoken-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cd69372e8c9dd761f0ab873112aba55a0e3e506332dd9f7522ca466e817b1b7a", size = 1258465 }, + { url = "https://files.pythonhosted.org/packages/de/a8/8f499c179ec900783ffe133e9aab10044481679bb9aad78436d239eee716/tiktoken-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ea0edb6f83dc56d794723286215918c1cde03712cbbafa0348b33448faf5b95", size = 894669 }, + { url = "https://files.pythonhosted.org/packages/c4/92/4d681b5c066d417b98f22a0176358d9e606e183c6b61c337d61fb54accb4/tiktoken-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c6386ca815e7d96ef5b4ac61e0048cd32ca5a92d5781255e13b31381d28667dc", size = 1066217 }, + { url = "https://files.pythonhosted.org/packages/12/dd/af27bbe186df481666de48cf0f2f4e0643ba9c78b472e7bf70144c663b22/tiktoken-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75f6d5db5bc2c6274b674ceab1615c1778e6416b14705827d19b40e6355f03e0", size = 1009441 }, + { url = "https://files.pythonhosted.org/packages/33/35/2792b7dcb8b150d2767322637513c73a3e80833c19212efea80b31087894/tiktoken-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e15b16f61e6f4625a57a36496d28dd182a8a60ec20a534c5343ba3cafa156ac7", size = 1144423 }, + { url = "https://files.pythonhosted.org/packages/65/ae/4d1682510172ce3500bbed3b206ebc4efefe280f0bf1179cfb043f88cc16/tiktoken-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebcec91babf21297022882344c3f7d9eed855931466c3311b1ad6b64befb3df", size = 1199002 }, + { url = "https://files.pythonhosted.org/packages/1c/2e/df2dc31dd161190f315829775a9652ea01d60f307af8f98e35bdd14a6a93/tiktoken-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e5fd49e7799579240f03913447c0cdfa1129625ebd5ac440787afc4345990427", size = 1260610 }, + { url = "https://files.pythonhosted.org/packages/70/22/e8fc1bf9cdecc439b7ddc28a45b976a8c699a38874c070749d855696368a/tiktoken-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:26242ca9dc8b58e875ff4ca078b9a94d2f0813e6a535dcd2205df5d49d927cc7", size = 894215 }, ] [[package]] @@ -3497,15 +3522,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] -[[package]] -name = "uritemplate" -version = "4.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d2/5a/4742fdba39cd02a56226815abfa72fe0aa81c33bed16ed045647d6000eba/uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0", size = 273898 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c0/7461b49cd25aeece13766f02ee576d1db528f1c37ce69aee300e075b485b/uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e", size = 10356 }, -] - [[package]] name = "urllib3" version = "2.3.0" From 4f2c36b958f37b00662a4a9051e0367611f0a59e Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 13:33:00 -0400 Subject: [PATCH 02/14] refactor: extract research agent --- ra_aid/__main__.py | 2 +- ra_aid/agent_utils.py | 470 +--------------------- ra_aid/agents/__init__.py | 11 +- ra_aid/agents/key_facts_gc_agent.py | 7 +- ra_aid/agents/key_snippets_gc_agent.py | 7 +- ra_aid/agents/research_agent.py | 523 +++++++++++++++++++++++++ ra_aid/tools/agent.py | 6 +- tests/ra_aid/tools/test_agent.py | 4 +- 8 files changed, 547 insertions(+), 483 deletions(-) create mode 100644 ra_aid/agents/research_agent.py diff --git a/ra_aid/__main__.py b/ra_aid/__main__.py index e4af7c2..d1b225a 100644 --- a/ra_aid/__main__.py +++ b/ra_aid/__main__.py @@ -35,8 +35,8 @@ from ra_aid.agent_utils import ( create_agent, run_agent_with_retry, run_planning_agent, - run_research_agent, ) +from ra_aid.agents.research_agent import run_research_agent from ra_aid.config import ( DEFAULT_MAX_TEST_CMD_RETRIES, DEFAULT_RECURSION_LIMIT, diff --git a/ra_aid/agent_utils.py b/ra_aid/agent_utils.py index 2593f13..5c02549 100644 --- a/ra_aid/agent_utils.py +++ b/ra_aid/agent_utils.py @@ -363,475 +363,7 @@ def create_agent( ) -def run_research_agent( - base_task_or_query: str, - model, - *, - expert_enabled: bool = False, - research_only: bool = False, - hil: bool = False, - web_research_enabled: bool = False, - memory: Optional[Any] = None, - thread_id: Optional[str] = None, - console_message: Optional[str] = None, -) -> Optional[str]: - """Run a research agent with the given configuration. - - Args: - base_task_or_query: The main task or query for research - model: The LLM model to use - expert_enabled: Whether expert mode is enabled - research_only: Whether this is a research-only task - hil: Whether human-in-the-loop mode is enabled - web_research_enabled: Whether web research is enabled - memory: Optional memory instance to use - config: Optional configuration dictionary - thread_id: Optional thread ID (defaults to new UUID) - console_message: Optional message to display before running - - Returns: - Optional[str]: The completion message if task completed successfully - - Example: - result = run_research_agent( - "Research Python async patterns", - model, - expert_enabled=True, - research_only=True - ) - """ - thread_id = thread_id or str(uuid.uuid4()) - logger.debug("Starting research agent with thread_id=%s", thread_id) - logger.debug( - "Research configuration: expert=%s, research_only=%s, hil=%s, web=%s", - expert_enabled, - research_only, - hil, - web_research_enabled, - ) - - if memory is None: - memory = MemorySaver() - - current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - working_directory = os.getcwd() - - # Get the last human input, if it exists - base_task = base_task_or_query - try: - human_input_repository = get_human_input_repository() - recent_inputs = human_input_repository.get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - last_human_input = recent_inputs[0].content - base_task = ( - f"{last_human_input}\n{base_task}" - ) - except RuntimeError as e: - logger.error(f"Failed to access human input repository: {str(e)}") - # Continue without appending last human input - - try: - key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) - except RuntimeError as e: - logger.error(f"Failed to access key fact repository: {str(e)}") - key_facts = "" - key_snippets = format_key_snippets_dict( - get_key_snippet_repository().get_snippets_dict() - ) - related_files = get_related_files() - - try: - project_info = get_project_info(".", file_limit=2000) - formatted_project_info = format_project_info(project_info) - except Exception as e: - logger.warning(f"Failed to get project info: {e}") - formatted_project_info = "" - - tools = get_research_tools( - research_only=research_only, - expert_enabled=expert_enabled, - human_interaction=hil, - web_research_enabled=get_config_repository().get("web_research_enabled", False), - ) - - # Get model info for reasoning assistance configuration - provider = get_config_repository().get("provider", "") - model_name = get_config_repository().get("model", "") - - # Get model configuration to check for reasoning_assist_default - model_config = {} - provider_models = models_params.get(provider, {}) - if provider_models and model_name in provider_models: - model_config = provider_models[model_name] - - # Check if reasoning assist is explicitly enabled/disabled - force_assistance = get_config_repository().get("force_reasoning_assistance", False) - disable_assistance = get_config_repository().get( - "disable_reasoning_assistance", False - ) - if force_assistance: - reasoning_assist_enabled = True - elif disable_assistance: - reasoning_assist_enabled = False - else: - # Fall back to model default - reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) - - logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) - expert_guidance = "" - - # Get research note information for reasoning assistance - try: - research_notes = format_research_notes_dict( - get_research_note_repository().get_notes_dict() - ) - except Exception as e: - logger.warning(f"Failed to get research notes: {e}") - research_notes = "" - - # If reasoning assist is enabled, make a one-off call to the expert model - if reasoning_assist_enabled: - try: - logger.info( - "Reasoning assist enabled for model %s, getting expert guidance", - model_name, - ) - - # Collect tool descriptions - tool_metadata = [] - from ra_aid.tools.reflection import get_function_info as get_tool_info - - for tool in tools: - try: - tool_info = get_tool_info(tool.func) - name = tool.func.__name__ - description = inspect.getdoc(tool.func) - tool_metadata.append(f"Tool: {name}\nDescription: {description}\n") - except Exception as e: - logger.warning(f"Error getting tool info for {tool}: {e}") - - # Format tool metadata - formatted_tool_metadata = "\n".join(tool_metadata) - - # Initialize expert model - expert_model = initialize_expert_llm(provider, model_name) - - # Format the reasoning assist prompt - reasoning_assist_prompt = REASONING_ASSIST_PROMPT_RESEARCH.format( - current_date=current_date, - working_directory=working_directory, - base_task=base_task, - key_facts=key_facts, - key_snippets=key_snippets, - research_notes=research_notes, - related_files=related_files, - env_inv=get_env_inv(), - tool_metadata=formatted_tool_metadata, - ) - - # Show the reasoning assist query in a panel - console.print( - Panel( - Markdown( - "Consulting with the reasoning model on the best research approach." - ), - title="📝 Thinking about research strategy...", - border_style="yellow", - ) - ) - - logger.debug("Invoking expert model for reasoning assist") - # Make the call to the expert model - response = expert_model.invoke(reasoning_assist_prompt) - - # Check if the model supports think tags - supports_think_tag = model_config.get("supports_think_tag", False) - supports_thinking = model_config.get("supports_thinking", False) - - # Get response content, handling if it's a list (for Claude thinking mode) - content = None - - if hasattr(response, "content"): - content = response.content - else: - # Fallback if content attribute is missing - content = str(response) - - # Process content based on its type - if isinstance(content, list): - # Handle structured thinking mode (e.g., Claude 3.7) - thinking_content = None - response_text = None - - # Process each item in the list - for item in content: - if isinstance(item, dict): - # Extract thinking content - if item.get("type") == "thinking" and "thinking" in item: - thinking_content = item["thinking"] - logger.debug("Found structured thinking content") - # Extract response text - elif item.get("type") == "text" and "text" in item: - response_text = item["text"] - logger.debug("Found structured response text") - - # Display thinking content in a separate panel if available - if thinking_content and get_config_repository().get( - "show_thoughts", False - ): - logger.debug( - f"Displaying structured thinking content ({len(thinking_content)} chars)" - ) - console.print( - Panel( - Markdown(thinking_content), - title="💭 Expert Thinking", - border_style="yellow", - ) - ) - - # Use response_text if available, otherwise fall back to joining - if response_text: - content = response_text - else: - # Fallback: join list items if structured extraction failed - logger.debug( - "No structured response text found, joining list items" - ) - content = "\n".join(str(item) for item in content) - elif supports_think_tag or supports_thinking: - # Process thinking content using the centralized function - content, _ = process_thinking_content( - content=content, - supports_think_tag=supports_think_tag, - supports_thinking=supports_thinking, - panel_title="💭 Expert Thinking", - panel_style="yellow", - logger=logger, - ) - - # Display the expert guidance in a panel - console.print( - Panel( - Markdown(content), - title="Research Strategy Guidance", - border_style="blue", - ) - ) - - # Use the content as expert guidance - expert_guidance = ( - content + "\n\nCONSULT WITH THE EXPERT FREQUENTLY DURING RESEARCH" - ) - - logger.info("Received expert guidance for research") - except Exception as e: - logger.error("Error getting expert guidance for research: %s", e) - expert_guidance = "" - - agent = create_agent(model, tools, checkpointer=memory, agent_type="research") - - expert_section = EXPERT_PROMPT_SECTION_RESEARCH if expert_enabled else "" - human_section = HUMAN_PROMPT_SECTION_RESEARCH if hil else "" - web_research_section = ( - WEB_RESEARCH_PROMPT_SECTION_RESEARCH - if get_config_repository().get("web_research_enabled") - else "" - ) - - # Prepare expert guidance section if expert guidance is available - expert_guidance_section = "" - if expert_guidance: - expert_guidance_section = f""" -{expert_guidance} -""" - - # Format research notes if available - # We get research notes earlier for reasoning assistance - - # Get environment inventory information - - prompt = (RESEARCH_ONLY_PROMPT if research_only else RESEARCH_PROMPT).format( - current_date=current_date, - working_directory=working_directory, - base_task=base_task, - research_only_note=( - "" - if research_only - else " Only request implementation if the user explicitly asked for changes to be made." - ), - expert_section=expert_section, - human_section=human_section, - web_research_section=web_research_section, - key_facts=key_facts, - work_log=get_work_log_repository().format_work_log(), - key_snippets=key_snippets, - related_files=related_files, - project_info=formatted_project_info, - new_project_hints=NEW_PROJECT_HINTS if project_info.is_new else "", - env_inv=get_env_inv(), - expert_guidance_section=expert_guidance_section, - ) - - config = get_config_repository().get_all() - recursion_limit = config.get("recursion_limit", DEFAULT_RECURSION_LIMIT) - run_config = { - "configurable": {"thread_id": thread_id}, - "recursion_limit": recursion_limit, - } - run_config.update(config) - - try: - if console_message: - console.print( - Panel(Markdown(console_message), title="🔬 Looking into it...") - ) - - if project_info: - display_project_status(project_info) - - if agent is not None: - logger.debug("Research agent created successfully") - none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry(agent, prompt, none_or_fallback_handler) - if _result: - # Log research completion - log_work_event(f"Completed research phase for: {base_task_or_query}") - return _result - else: - logger.debug("No model provided, running web research tools directly") - return run_web_research_agent( - base_task_or_query, - model=None, - expert_enabled=expert_enabled, - hil=hil, - web_research_enabled=web_research_enabled, - memory=memory, - thread_id=thread_id, - console_message=console_message, - ) - except (KeyboardInterrupt, AgentInterrupt): - raise - except Exception as e: - logger.error("Research agent failed: %s", str(e), exc_info=True) - raise - - -def run_web_research_agent( - query: str, - model, - *, - expert_enabled: bool = False, - hil: bool = False, - web_research_enabled: bool = False, - memory: Optional[Any] = None, - thread_id: Optional[str] = None, - console_message: Optional[str] = None, -) -> Optional[str]: - """Run a web research agent with the given configuration. - - Args: - query: The mainquery for web research - model: The LLM model to use - expert_enabled: Whether expert mode is enabled - hil: Whether human-in-the-loop mode is enabled - web_research_enabled: Whether web research is enabled - memory: Optional memory instance to use - config: Optional configuration dictionary - thread_id: Optional thread ID (defaults to new UUID) - console_message: Optional message to display before running - - Returns: - Optional[str]: The completion message if task completed successfully - - Example: - result = run_web_research_agent( - "Research latest Python async patterns", - model, - expert_enabled=True - ) - """ - thread_id = thread_id or str(uuid.uuid4()) - logger.debug("Starting web research agent with thread_id=%s", thread_id) - logger.debug( - "Web research configuration: expert=%s, hil=%s, web=%s", - expert_enabled, - hil, - web_research_enabled, - ) - - if memory is None: - memory = MemorySaver() - - if thread_id is None: - thread_id = str(uuid.uuid4()) - - tools = get_web_research_tools(expert_enabled=expert_enabled) - - agent = create_agent(model, tools, checkpointer=memory, agent_type="research") - - expert_section = EXPERT_PROMPT_SECTION_RESEARCH if expert_enabled else "" - human_section = HUMAN_PROMPT_SECTION_RESEARCH if hil else "" - - try: - key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) - except RuntimeError as e: - logger.error(f"Failed to access key fact repository: {str(e)}") - key_facts = "" - try: - key_snippets = format_key_snippets_dict( - get_key_snippet_repository().get_snippets_dict() - ) - except RuntimeError as e: - logger.error(f"Failed to access key snippet repository: {str(e)}") - key_snippets = "" - related_files = get_related_files() - - current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - working_directory = os.getcwd() - - # Get environment inventory information - - prompt = WEB_RESEARCH_PROMPT.format( - current_date=current_date, - working_directory=working_directory, - web_research_query=query, - expert_section=expert_section, - human_section=human_section, - key_facts=key_facts, - work_log=get_work_log_repository().format_work_log(), - key_snippets=key_snippets, - related_files=related_files, - env_inv=get_env_inv(), - ) - - config = get_config_repository().get_all() - - recursion_limit = config.get("recursion_limit", DEFAULT_RECURSION_LIMIT) - run_config = { - "configurable": {"thread_id": thread_id}, - "recursion_limit": recursion_limit, - } - if config: - run_config.update(config) - - try: - if console_message: - console.print(Panel(Markdown(console_message), title="🔬 Researching...")) - - logger.debug("Web research agent completed successfully") - none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry(agent, prompt, none_or_fallback_handler) - if _result: - # Log web research completion - log_work_event(f"Completed web research phase for: {query}") - return _result - - except (KeyboardInterrupt, AgentInterrupt): - raise - except Exception as e: - logger.error("Web research agent failed: %s", str(e), exc_info=True) - raise +from ra_aid.agents.research_agent import run_research_agent, run_web_research_agent def run_planning_agent( diff --git a/ra_aid/agents/__init__.py b/ra_aid/agents/__init__.py index e04c27a..11ea035 100644 --- a/ra_aid/agents/__init__.py +++ b/ra_aid/agents/__init__.py @@ -3,16 +3,23 @@ Agent package for various specialized agents. This package contains agents responsible for specific tasks such as cleaning up key facts and key snippets in the database when they -exceed certain thresholds. +exceed certain thresholds, as well as performing research tasks. Includes agents for: - Key facts garbage collection - Key snippets garbage collection +- Research tasks """ from typing import Optional from ra_aid.agents.key_facts_gc_agent import run_key_facts_gc_agent from ra_aid.agents.key_snippets_gc_agent import run_key_snippets_gc_agent +from ra_aid.agents.research_agent import run_research_agent, run_web_research_agent -__all__ = ["run_key_facts_gc_agent", "run_key_snippets_gc_agent"] \ No newline at end of file +__all__ = [ + "run_key_facts_gc_agent", + "run_key_snippets_gc_agent", + "run_research_agent", + "run_web_research_agent" +] \ No newline at end of file diff --git a/ra_aid/agents/key_facts_gc_agent.py b/ra_aid/agents/key_facts_gc_agent.py index a2b1115..d158c64 100644 --- a/ra_aid/agents/key_facts_gc_agent.py +++ b/ra_aid/agents/key_facts_gc_agent.py @@ -17,7 +17,8 @@ from rich.panel import Panel logger = logging.getLogger(__name__) from ra_aid.agent_context import mark_should_exit -from ra_aid.agent_utils import create_agent, run_agent_with_retry +# Import agent_utils functions at runtime to avoid circular imports +from ra_aid import agent_utils from ra_aid.database.repositories.key_fact_repository import get_key_fact_repository from ra_aid.database.repositories.human_input_repository import get_human_input_repository from ra_aid.database.repositories.config_repository import get_config_repository @@ -164,7 +165,7 @@ def run_key_facts_gc_agent() -> None: ) # Create the agent with the delete_key_facts tool - agent = create_agent(model, [delete_key_facts]) + agent = agent_utils.create_agent(model, [delete_key_facts]) # Format the prompt with the eligible facts prompt = KEY_FACTS_GC_PROMPT.format(key_facts=formatted_facts) @@ -175,7 +176,7 @@ def run_key_facts_gc_agent() -> None: } # Run the agent - run_agent_with_retry(agent, prompt, agent_config) + agent_utils.run_agent_with_retry(agent, prompt, agent_config) # Get updated count try: diff --git a/ra_aid/agents/key_snippets_gc_agent.py b/ra_aid/agents/key_snippets_gc_agent.py index 43e0473..b9d55ef 100644 --- a/ra_aid/agents/key_snippets_gc_agent.py +++ b/ra_aid/agents/key_snippets_gc_agent.py @@ -13,7 +13,8 @@ from rich.console import Console from rich.markdown import Markdown from rich.panel import Panel -from ra_aid.agent_utils import create_agent, run_agent_with_retry +# Import agent_utils functions at runtime to avoid circular imports +from ra_aid import agent_utils from ra_aid.database.repositories.key_snippet_repository import get_key_snippet_repository from ra_aid.database.repositories.human_input_repository import get_human_input_repository from ra_aid.database.repositories.config_repository import get_config_repository @@ -168,7 +169,7 @@ def run_key_snippets_gc_agent() -> None: ) # Create the agent with the delete_key_snippets tool - agent = create_agent(model, [delete_key_snippets]) + agent = agent_utils.create_agent(model, [delete_key_snippets]) # Format the prompt with the eligible snippets prompt = KEY_SNIPPETS_GC_PROMPT.format(key_snippets=formatted_snippets) @@ -179,7 +180,7 @@ def run_key_snippets_gc_agent() -> None: } # Run the agent - run_agent_with_retry(agent, prompt, agent_config) + agent_utils.run_agent_with_retry(agent, prompt, agent_config) # Get updated count updated_snippets = get_key_snippet_repository().get_all() diff --git a/ra_aid/agents/research_agent.py b/ra_aid/agents/research_agent.py new file mode 100644 index 0000000..61fbc05 --- /dev/null +++ b/ra_aid/agents/research_agent.py @@ -0,0 +1,523 @@ +""" +Research agent implementation. + +This module provides functionality for running a research agent to investigate tasks +and queries. The agent can perform both general research and web-specific research +tasks, with options for expert guidance and human-in-the-loop collaboration. +""" + +import inspect +import os +import uuid +from datetime import datetime +from typing import Any, Optional + +from langchain_core.messages import SystemMessage +from rich.console import Console +from rich.markdown import Markdown +from rich.panel import Panel + +from ra_aid.agent_context import agent_context, is_completed, reset_completion_flags, should_exit +# Import agent_utils functions at runtime to avoid circular imports +from ra_aid import agent_utils +from ra_aid.console.formatting import print_error +from ra_aid.database.repositories.key_fact_repository import get_key_fact_repository +from ra_aid.database.repositories.key_snippet_repository import get_key_snippet_repository +from ra_aid.database.repositories.human_input_repository import get_human_input_repository +from ra_aid.database.repositories.research_note_repository import get_research_note_repository +from ra_aid.database.repositories.config_repository import get_config_repository +from ra_aid.database.repositories.work_log_repository import get_work_log_repository +from ra_aid.env_inv_context import get_env_inv +from ra_aid.exceptions import AgentInterrupt +from ra_aid.llm import initialize_expert_llm +from ra_aid.logging_config import get_logger +from ra_aid.model_formatters import format_key_facts_dict +from ra_aid.model_formatters.key_snippets_formatter import format_key_snippets_dict +from ra_aid.model_formatters.research_notes_formatter import format_research_notes_dict +from ra_aid.models_params import models_params +from ra_aid.project_info import display_project_status, format_project_info, get_project_info +from ra_aid.prompts.expert_prompts import EXPERT_PROMPT_SECTION_RESEARCH +from ra_aid.prompts.human_prompts import HUMAN_PROMPT_SECTION_RESEARCH +from ra_aid.prompts.research_prompts import RESEARCH_ONLY_PROMPT, RESEARCH_PROMPT +from ra_aid.prompts.reasoning_assist_prompt import REASONING_ASSIST_PROMPT_RESEARCH +from ra_aid.prompts.web_research_prompts import ( + WEB_RESEARCH_PROMPT, + WEB_RESEARCH_PROMPT_SECTION_RESEARCH, +) +from ra_aid.prompts.common_prompts import NEW_PROJECT_HINTS +from ra_aid.tool_configs import get_research_tools, get_web_research_tools +from ra_aid.tools.memory import get_related_files, log_work_event + +logger = get_logger(__name__) +console = Console() + + +def run_research_agent( + base_task_or_query: str, + model, + *, + expert_enabled: bool = False, + research_only: bool = False, + hil: bool = False, + web_research_enabled: bool = False, + memory: Optional[Any] = None, + thread_id: Optional[str] = None, + console_message: Optional[str] = None, +) -> Optional[str]: + """Run a research agent with the given configuration. + + Args: + base_task_or_query: The main task or query for research + model: The LLM model to use + expert_enabled: Whether expert mode is enabled + research_only: Whether this is a research-only task + hil: Whether human-in-the-loop mode is enabled + web_research_enabled: Whether web research is enabled + memory: Optional memory instance to use + thread_id: Optional thread ID (defaults to new UUID) + console_message: Optional message to display before running + + Returns: + Optional[str]: The completion message if task completed successfully + + Example: + result = run_research_agent( + "Research Python async patterns", + model, + expert_enabled=True, + research_only=True + ) + """ + thread_id = thread_id or str(uuid.uuid4()) + logger.debug("Starting research agent with thread_id=%s", thread_id) + logger.debug( + "Research configuration: expert=%s, research_only=%s, hil=%s, web=%s", + expert_enabled, + research_only, + hil, + web_research_enabled, + ) + + if memory is None: + from langgraph.checkpoint.memory import MemorySaver + memory = MemorySaver() + + current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + working_directory = os.getcwd() + + # Get the last human input, if it exists + base_task = base_task_or_query + try: + human_input_repository = get_human_input_repository() + recent_inputs = human_input_repository.get_recent(1) + if recent_inputs and len(recent_inputs) > 0: + last_human_input = recent_inputs[0].content + base_task = ( + f"{last_human_input}\n{base_task}" + ) + except RuntimeError as e: + logger.error(f"Failed to access human input repository: {str(e)}") + # Continue without appending last human input + + try: + key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) + except RuntimeError as e: + logger.error(f"Failed to access key fact repository: {str(e)}") + key_facts = "" + key_snippets = format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ) + related_files = get_related_files() + + try: + project_info = get_project_info(".", file_limit=2000) + formatted_project_info = format_project_info(project_info) + except Exception as e: + logger.warning(f"Failed to get project info: {e}") + formatted_project_info = "" + + tools = get_research_tools( + research_only=research_only, + expert_enabled=expert_enabled, + human_interaction=hil, + web_research_enabled=get_config_repository().get("web_research_enabled", False), + ) + + # Get model info for reasoning assistance configuration + provider = get_config_repository().get("provider", "") + model_name = get_config_repository().get("model", "") + + # Get model configuration to check for reasoning_assist_default + model_config = {} + provider_models = models_params.get(provider, {}) + if provider_models and model_name in provider_models: + model_config = provider_models[model_name] + + # Check if reasoning assist is explicitly enabled/disabled + force_assistance = get_config_repository().get("force_reasoning_assistance", False) + disable_assistance = get_config_repository().get( + "disable_reasoning_assistance", False + ) + if force_assistance: + reasoning_assist_enabled = True + elif disable_assistance: + reasoning_assist_enabled = False + else: + # Fall back to model default + reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) + + logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) + expert_guidance = "" + + # Get research note information for reasoning assistance + try: + research_notes = format_research_notes_dict( + get_research_note_repository().get_notes_dict() + ) + except Exception as e: + logger.warning(f"Failed to get research notes: {e}") + research_notes = "" + + # If reasoning assist is enabled, make a one-off call to the expert model + if reasoning_assist_enabled: + try: + logger.info( + "Reasoning assist enabled for model %s, getting expert guidance", + model_name, + ) + + # Collect tool descriptions + tool_metadata = [] + from ra_aid.tools.reflection import get_function_info as get_tool_info + + for tool in tools: + try: + tool_info = get_tool_info(tool.func) + name = tool.func.__name__ + description = inspect.getdoc(tool.func) + tool_metadata.append(f"Tool: {name}\nDescription: {description}\n") + except Exception as e: + logger.warning(f"Error getting tool info for {tool}: {e}") + + # Format tool metadata + formatted_tool_metadata = "\n".join(tool_metadata) + + # Initialize expert model + expert_model = initialize_expert_llm(provider, model_name) + + # Format the reasoning assist prompt + reasoning_assist_prompt = REASONING_ASSIST_PROMPT_RESEARCH.format( + current_date=current_date, + working_directory=working_directory, + base_task=base_task, + key_facts=key_facts, + key_snippets=key_snippets, + research_notes=research_notes, + related_files=related_files, + env_inv=get_env_inv(), + tool_metadata=formatted_tool_metadata, + ) + + # Show the reasoning assist query in a panel + console.print( + Panel( + Markdown( + "Consulting with the reasoning model on the best research approach." + ), + title="📝 Thinking about research strategy...", + border_style="yellow", + ) + ) + + logger.debug("Invoking expert model for reasoning assist") + # Make the call to the expert model + response = expert_model.invoke(reasoning_assist_prompt) + + # Check if the model supports think tags + supports_think_tag = model_config.get("supports_think_tag", False) + supports_thinking = model_config.get("supports_thinking", False) + + # Get response content, handling if it's a list (for Claude thinking mode) + content = None + + if hasattr(response, "content"): + content = response.content + else: + # Fallback if content attribute is missing + content = str(response) + + # Process content based on its type + if isinstance(content, list): + # Handle structured thinking mode (e.g., Claude 3.7) + thinking_content = None + response_text = None + + # Process each item in the list + for item in content: + if isinstance(item, dict): + # Extract thinking content + if item.get("type") == "thinking" and "thinking" in item: + thinking_content = item["thinking"] + logger.debug("Found structured thinking content") + # Extract response text + elif item.get("type") == "text" and "text" in item: + response_text = item["text"] + logger.debug("Found structured response text") + + # Display thinking content in a separate panel if available + if thinking_content and get_config_repository().get( + "show_thoughts", False + ): + logger.debug( + f"Displaying structured thinking content ({len(thinking_content)} chars)" + ) + console.print( + Panel( + Markdown(thinking_content), + title="💭 Expert Thinking", + border_style="yellow", + ) + ) + + # Use response_text if available, otherwise fall back to joining + if response_text: + content = response_text + else: + # Fallback: join list items if structured extraction failed + logger.debug( + "No structured response text found, joining list items" + ) + content = "\n".join(str(item) for item in content) + elif supports_think_tag or supports_thinking: + # Process thinking content using the centralized function + content, _ = agent_utils.process_thinking_content( + content=content, + supports_think_tag=supports_think_tag, + supports_thinking=supports_thinking, + panel_title="💭 Expert Thinking", + panel_style="yellow", + logger=logger, + ) + + # Display the expert guidance in a panel + console.print( + Panel( + Markdown(content), + title="Research Strategy Guidance", + border_style="blue", + ) + ) + + # Use the content as expert guidance + expert_guidance = ( + content + "\n\nCONSULT WITH THE EXPERT FREQUENTLY DURING RESEARCH" + ) + + logger.info("Received expert guidance for research") + except Exception as e: + logger.error("Error getting expert guidance for research: %s", e) + expert_guidance = "" + + agent = agent_utils.create_agent(model, tools, checkpointer=memory, agent_type="research") + + expert_section = EXPERT_PROMPT_SECTION_RESEARCH if expert_enabled else "" + human_section = HUMAN_PROMPT_SECTION_RESEARCH if hil else "" + web_research_section = ( + WEB_RESEARCH_PROMPT_SECTION_RESEARCH + if get_config_repository().get("web_research_enabled") + else "" + ) + + # Prepare expert guidance section if expert guidance is available + expert_guidance_section = "" + if expert_guidance: + expert_guidance_section = f""" +{expert_guidance} +""" + + # Format research notes if available + # We get research notes earlier for reasoning assistance + + # Get environment inventory information + + prompt = (RESEARCH_ONLY_PROMPT if research_only else RESEARCH_PROMPT).format( + current_date=current_date, + working_directory=working_directory, + base_task=base_task, + research_only_note=( + "" + if research_only + else " Only request implementation if the user explicitly asked for changes to be made." + ), + expert_section=expert_section, + human_section=human_section, + web_research_section=web_research_section, + key_facts=key_facts, + work_log=get_work_log_repository().format_work_log(), + key_snippets=key_snippets, + related_files=related_files, + project_info=formatted_project_info, + new_project_hints=NEW_PROJECT_HINTS if project_info.is_new else "", + env_inv=get_env_inv(), + expert_guidance_section=expert_guidance_section, + ) + + config = get_config_repository().get_all() + recursion_limit = config.get("recursion_limit", 100) + run_config = { + "configurable": {"thread_id": thread_id}, + "recursion_limit": recursion_limit, + } + run_config.update(config) + + try: + if console_message: + console.print( + Panel(Markdown(console_message), title="🔬 Looking into it...") + ) + + if project_info: + display_project_status(project_info) + + if agent is not None: + logger.debug("Research agent created successfully") + none_or_fallback_handler = agent_utils.init_fallback_handler(agent, tools) + _result = agent_utils.run_agent_with_retry(agent, prompt, none_or_fallback_handler) + if _result: + # Log research completion + log_work_event(f"Completed research phase for: {base_task_or_query}") + return _result + else: + logger.debug("No model provided, running web research tools directly") + return run_web_research_agent( + base_task_or_query, + model=None, + expert_enabled=expert_enabled, + hil=hil, + web_research_enabled=web_research_enabled, + memory=memory, + thread_id=thread_id, + console_message=console_message, + ) + except (KeyboardInterrupt, AgentInterrupt): + raise + except Exception as e: + logger.error("Research agent failed: %s", str(e), exc_info=True) + raise + + +def run_web_research_agent( + query: str, + model, + *, + expert_enabled: bool = False, + hil: bool = False, + web_research_enabled: bool = False, + memory: Optional[Any] = None, + thread_id: Optional[str] = None, + console_message: Optional[str] = None, +) -> Optional[str]: + """Run a web research agent with the given configuration. + + Args: + query: The mainquery for web research + model: The LLM model to use + expert_enabled: Whether expert mode is enabled + hil: Whether human-in-the-loop mode is enabled + web_research_enabled: Whether web research is enabled + memory: Optional memory instance to use + thread_id: Optional thread ID (defaults to new UUID) + console_message: Optional message to display before running + + Returns: + Optional[str]: The completion message if task completed successfully + + Example: + result = run_web_research_agent( + "Research latest Python async patterns", + model, + expert_enabled=True + ) + """ + thread_id = thread_id or str(uuid.uuid4()) + logger.debug("Starting web research agent with thread_id=%s", thread_id) + logger.debug( + "Web research configuration: expert=%s, hil=%s, web=%s", + expert_enabled, + hil, + web_research_enabled, + ) + + if memory is None: + from langgraph.checkpoint.memory import MemorySaver + memory = MemorySaver() + + if thread_id is None: + thread_id = str(uuid.uuid4()) + + tools = get_web_research_tools(expert_enabled=expert_enabled) + + agent = agent_utils.create_agent(model, tools, checkpointer=memory, agent_type="research") + + expert_section = EXPERT_PROMPT_SECTION_RESEARCH if expert_enabled else "" + human_section = HUMAN_PROMPT_SECTION_RESEARCH if hil else "" + + try: + key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) + except RuntimeError as e: + logger.error(f"Failed to access key fact repository: {str(e)}") + key_facts = "" + try: + key_snippets = format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ) + except RuntimeError as e: + logger.error(f"Failed to access key snippet repository: {str(e)}") + key_snippets = "" + related_files = get_related_files() + + current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + working_directory = os.getcwd() + + # Get environment inventory information + + prompt = WEB_RESEARCH_PROMPT.format( + current_date=current_date, + working_directory=working_directory, + web_research_query=query, + expert_section=expert_section, + human_section=human_section, + key_facts=key_facts, + work_log=get_work_log_repository().format_work_log(), + key_snippets=key_snippets, + related_files=related_files, + env_inv=get_env_inv(), + ) + + config = get_config_repository().get_all() + + recursion_limit = config.get("recursion_limit", 100) + run_config = { + "configurable": {"thread_id": thread_id}, + "recursion_limit": recursion_limit, + } + if config: + run_config.update(config) + + try: + if console_message: + console.print(Panel(Markdown(console_message), title="🔬 Researching...")) + + logger.debug("Web research agent completed successfully") + none_or_fallback_handler = init_fallback_handler(agent, tools) + _result = run_agent_with_retry(agent, prompt, none_or_fallback_handler) + if _result: + # Log web research completion + log_work_event(f"Completed web research phase for: {query}") + return _result + + except (KeyboardInterrupt, AgentInterrupt): + raise + except Exception as e: + logger.error("Web research agent failed: %s", str(e), exc_info=True) + raise \ No newline at end of file diff --git a/ra_aid/tools/agent.py b/ra_aid/tools/agent.py index 343fa6b..eabcbf7 100644 --- a/ra_aid/tools/agent.py +++ b/ra_aid/tools/agent.py @@ -90,7 +90,7 @@ def request_research(query: str) -> ResearchResult: try: # Run research agent - from ..agent_utils import run_research_agent + from ..agents.research_agent import run_research_agent _result = run_research_agent( query, @@ -177,7 +177,7 @@ def request_web_research(query: str) -> ResearchResult: try: # Run web research agent - from ..agent_utils import run_web_research_agent + from ..agents.research_agent import run_web_research_agent _result = run_web_research_agent( query, @@ -254,7 +254,7 @@ def request_research_and_implementation(query: str) -> Dict[str, Any]: try: # Run research agent - from ..agent_utils import run_research_agent + from ..agents.research_agent import run_research_agent _result = run_research_agent( query, diff --git a/tests/ra_aid/tools/test_agent.py b/tests/ra_aid/tools/test_agent.py index 53b4e6e..a8d8f1c 100644 --- a/tests/ra_aid/tools/test_agent.py +++ b/tests/ra_aid/tools/test_agent.py @@ -155,7 +155,7 @@ def mock_functions(): def test_request_research_uses_key_fact_repository(reset_memory, mock_functions): """Test that request_research uses KeyFactRepository directly with formatting.""" # Mock running the research agent - with patch('ra_aid.agent_utils.run_research_agent'): + with patch('ra_aid.agents.research_agent.run_research_agent'): # Call the function result = request_research("test query") @@ -197,7 +197,7 @@ def test_request_research_max_depth(reset_memory, mock_functions): def test_request_research_and_implementation_uses_key_fact_repository(reset_memory, mock_functions): """Test that request_research_and_implementation uses KeyFactRepository correctly.""" # Mock running the research agent - with patch('ra_aid.agent_utils.run_research_agent'): + with patch('ra_aid.agents.research_agent.run_research_agent'): # Call the function result = request_research_and_implementation("test query") From a437a1e8c351a8ae6ab287a25cfb4e95ea67c48b Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 13:46:08 -0400 Subject: [PATCH 03/14] prompt improvements --- ra_aid/prompts/reasoning_assist_prompt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ra_aid/prompts/reasoning_assist_prompt.py b/ra_aid/prompts/reasoning_assist_prompt.py index 39f2e92..075dad5 100644 --- a/ra_aid/prompts/reasoning_assist_prompt.py +++ b/ra_aid/prompts/reasoning_assist_prompt.py @@ -88,6 +88,7 @@ REMEMBER, IT IS *IMPERATIVE* TO RECORD KEY INFO SUCH AS BUILD/TEST COMMANDS, ETC WE DO NOT WANT TO EMIT REDUNDANT KEY FACTS, SNIPPETS, ETC. WE DO NOT WANT TO EXCESSIVELY EMIT TINY KEY SNIPPETS --THEY SHOULD BE "paragraphs" OF CODE TYPICALLY. IF THERE IS COMPLEX LOGIC, COMPILATION ERRORS, DEBUGGING, THE AGENT SHOULD USE ask_expert. +IF ANYTHING AT ALL GOES WRONG, CALL ask_expert. Given the available information, tools, and base task, write a couple paragraphs about how an agentic system might use the available tools to implement the given task definition. The agent will be writing code and making changes at this point. From d8dcc8ca861ba483c202ad8d88b96bf5a23bbbb1 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 14:19:06 -0400 Subject: [PATCH 04/14] refactor: extract run_planning_agent --- ra_aid/__main__.py | 2 +- ra_aid/agent_utils.py | 315 --------------------------- ra_aid/agents/__init__.py | 8 +- ra_aid/agents/planning_agent.py | 361 +++++++++++++++++++++++++++++++ ra_aid/tools/agent.py | 2 +- tests/ra_aid/test_main.py | 4 +- tests/ra_aid/tools/test_agent.py | 2 +- 7 files changed, 372 insertions(+), 322 deletions(-) create mode 100644 ra_aid/agents/planning_agent.py diff --git a/ra_aid/__main__.py b/ra_aid/__main__.py index d1b225a..dfd7bfa 100644 --- a/ra_aid/__main__.py +++ b/ra_aid/__main__.py @@ -34,9 +34,9 @@ from ra_aid.version_check import check_for_newer_version from ra_aid.agent_utils import ( create_agent, run_agent_with_retry, - run_planning_agent, ) from ra_aid.agents.research_agent import run_research_agent +from ra_aid.agents import run_planning_agent from ra_aid.config import ( DEFAULT_MAX_TEST_CMD_RETRIES, DEFAULT_RECURSION_LIMIT, diff --git a/ra_aid/agent_utils.py b/ra_aid/agent_utils.py index 5c02549..8d1d165 100644 --- a/ra_aid/agent_utils.py +++ b/ra_aid/agent_utils.py @@ -366,321 +366,6 @@ def create_agent( from ra_aid.agents.research_agent import run_research_agent, run_web_research_agent -def run_planning_agent( - base_task: str, - model, - *, - expert_enabled: bool = False, - hil: bool = False, - memory: Optional[Any] = None, - thread_id: Optional[str] = None, -) -> Optional[str]: - """Run a planning agent to create implementation plans. - - Args: - base_task: The main task to plan implementation for - model: The LLM model to use - expert_enabled: Whether expert mode is enabled - hil: Whether human-in-the-loop mode is enabled - memory: Optional memory instance to use - thread_id: Optional thread ID (defaults to new UUID) - - Returns: - Optional[str]: The completion message if planning completed successfully - """ - thread_id = thread_id or str(uuid.uuid4()) - logger.debug("Starting planning agent with thread_id=%s", thread_id) - logger.debug("Planning configuration: expert=%s, hil=%s", expert_enabled, hil) - - if memory is None: - memory = MemorySaver() - - if thread_id is None: - thread_id = str(uuid.uuid4()) - - # Get latest project info - try: - project_info = get_project_info(".") - formatted_project_info = format_project_info(project_info) - except Exception as e: - logger.warning("Failed to get project info: %s", str(e)) - formatted_project_info = "Project info unavailable" - - tools = get_planning_tools( - expert_enabled=expert_enabled, - web_research_enabled=get_config_repository().get("web_research_enabled", False), - ) - - # Get model configuration - provider = get_config_repository().get("provider", "") - model_name = get_config_repository().get("model", "") - logger.debug("Checking for reasoning_assist_default on %s/%s", provider, model_name) - - # Get model configuration to check for reasoning_assist_default - model_config = {} - provider_models = models_params.get(provider, {}) - if provider_models and model_name in provider_models: - model_config = provider_models[model_name] - - # Check if reasoning assist is explicitly enabled/disabled - force_assistance = get_config_repository().get("force_reasoning_assistance", False) - disable_assistance = get_config_repository().get( - "disable_reasoning_assistance", False - ) - - if force_assistance: - reasoning_assist_enabled = True - elif disable_assistance: - reasoning_assist_enabled = False - else: - # Fall back to model default - reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) - - logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) - - # Get all the context information (used both for normal planning and reasoning assist) - current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - working_directory = os.getcwd() - - # Make sure key_facts is defined before using it - try: - key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) - except RuntimeError as e: - logger.error(f"Failed to access key fact repository: {str(e)}") - key_facts = "" - - # Make sure key_snippets is defined before using it - try: - key_snippets = format_key_snippets_dict( - get_key_snippet_repository().get_snippets_dict() - ) - except RuntimeError as e: - logger.error(f"Failed to access key snippet repository: {str(e)}") - key_snippets = "" - - # Get formatted research notes using repository - try: - repository = get_research_note_repository() - notes_dict = repository.get_notes_dict() - formatted_research_notes = format_research_notes_dict(notes_dict) - except RuntimeError as e: - logger.error(f"Failed to access research note repository: {str(e)}") - formatted_research_notes = "" - - # Get related files - related_files = "\n".join(get_related_files()) - - # Get environment inventory information - env_inv = get_env_inv() - - # Display the planning stage header before any reasoning assistance - print_stage_header("Planning Stage") - - # Initialize expert guidance section - expert_guidance = "" - - # If reasoning assist is enabled, make a one-off call to the expert model - if reasoning_assist_enabled: - try: - logger.info( - "Reasoning assist enabled for model %s, getting expert guidance", - model_name, - ) - - # Collect tool descriptions - tool_metadata = [] - from ra_aid.tools.reflection import get_function_info as get_tool_info - - for tool in tools: - try: - tool_info = get_tool_info(tool.func) - name = tool.func.__name__ - description = inspect.getdoc(tool.func) - tool_metadata.append(f"Tool: {name}\nDescription: {description}\n") - except Exception as e: - logger.warning(f"Error getting tool info for {tool}: {e}") - - # Format tool metadata - formatted_tool_metadata = "\n".join(tool_metadata) - - # Initialize expert model - expert_model = initialize_expert_llm(provider, model_name) - - # Format the reasoning assist prompt - reasoning_assist_prompt = REASONING_ASSIST_PROMPT_PLANNING.format( - current_date=current_date, - working_directory=working_directory, - base_task=base_task, - key_facts=key_facts, - key_snippets=key_snippets, - research_notes=formatted_research_notes, - related_files=related_files, - env_inv=env_inv, - tool_metadata=formatted_tool_metadata, - ) - - # Show the reasoning assist query in a panel - console.print( - Panel( - Markdown( - "Consulting with the reasoning model on the best way to do this." - ), - title="📝 Thinking about the plan...", - border_style="yellow", - ) - ) - - logger.debug("Invoking expert model for reasoning assist") - # Make the call to the expert model - response = expert_model.invoke(reasoning_assist_prompt) - - # Check if the model supports think tags - supports_think_tag = model_config.get("supports_think_tag", False) - supports_thinking = model_config.get("supports_thinking", False) - - # Get response content, handling if it's a list (for Claude thinking mode) - content = None - - if hasattr(response, "content"): - content = response.content - else: - # Fallback if content attribute is missing - content = str(response) - - # Process content based on its type - if isinstance(content, list): - # Handle structured thinking mode (e.g., Claude 3.7) - thinking_content = None - response_text = None - - # Process each item in the list - for item in content: - if isinstance(item, dict): - # Extract thinking content - if item.get("type") == "thinking" and "thinking" in item: - thinking_content = item["thinking"] - logger.debug("Found structured thinking content") - # Extract response text - elif item.get("type") == "text" and "text" in item: - response_text = item["text"] - logger.debug("Found structured response text") - - # Display thinking content in a separate panel if available - if thinking_content and get_config_repository().get( - "show_thoughts", False - ): - logger.debug( - f"Displaying structured thinking content ({len(thinking_content)} chars)" - ) - console.print( - Panel( - Markdown(thinking_content), - title="💭 Expert Thinking", - border_style="yellow", - ) - ) - - # Use response_text if available, otherwise fall back to joining - if response_text: - content = response_text - else: - # Fallback: join list items if structured extraction failed - logger.debug( - "No structured response text found, joining list items" - ) - content = "\n".join(str(item) for item in content) - elif supports_think_tag or supports_thinking: - # Process thinking content using the centralized function - content, _ = process_thinking_content( - content=content, - supports_think_tag=supports_think_tag, - supports_thinking=supports_thinking, - panel_title="💭 Expert Thinking", - panel_style="yellow", - logger=logger, - ) - - # Display the expert guidance in a panel - console.print( - Panel( - Markdown(content), title="Reasoning Guidance", border_style="blue" - ) - ) - - # Use the content as expert guidance - expert_guidance = ( - content + "\n\nCONSULT WITH THE EXPERT FREQUENTLY ON THIS TASK" - ) - - logger.info("Received expert guidance for planning") - except Exception as e: - logger.error("Error getting expert guidance for planning: %s", e) - expert_guidance = "" - - agent = create_agent(model, tools, checkpointer=memory, agent_type="planner") - - expert_section = EXPERT_PROMPT_SECTION_PLANNING if expert_enabled else "" - human_section = HUMAN_PROMPT_SECTION_PLANNING if hil else "" - web_research_section = ( - WEB_RESEARCH_PROMPT_SECTION_PLANNING - if get_config_repository().get("web_research_enabled", False) - else "" - ) - - # Prepare expert guidance section if expert guidance is available - expert_guidance_section = "" - if expert_guidance: - expert_guidance_section = f""" -{expert_guidance} -""" - - planning_prompt = PLANNING_PROMPT.format( - current_date=current_date, - working_directory=working_directory, - expert_section=expert_section, - human_section=human_section, - web_research_section=web_research_section, - base_task=base_task, - project_info=formatted_project_info, - research_notes=formatted_research_notes, - related_files=related_files, - key_facts=key_facts, - key_snippets=key_snippets, - work_log=get_work_log_repository().format_work_log(), - research_only_note=( - "" - if get_config_repository().get("research_only", False) - else " Only request implementation if the user explicitly asked for changes to be made." - ), - env_inv=env_inv, - expert_guidance_section=expert_guidance_section, - ) - - config_values = get_config_repository().get_all() - recursion_limit = get_config_repository().get( - "recursion_limit", DEFAULT_RECURSION_LIMIT - ) - run_config = { - "configurable": {"thread_id": thread_id}, - "recursion_limit": recursion_limit, - } - run_config.update(config_values) - - try: - logger.debug("Planning agent completed successfully") - none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry(agent, planning_prompt, none_or_fallback_handler) - if _result: - # Log planning completion - log_work_event(f"Completed planning phase for: {base_task}") - return _result - except (KeyboardInterrupt, AgentInterrupt): - raise - except Exception as e: - logger.error("Planning agent failed: %s", str(e), exc_info=True) - raise - - def run_task_implementation_agent( base_task: str, tasks: list, diff --git a/ra_aid/agents/__init__.py b/ra_aid/agents/__init__.py index 11ea035..ecf2f8a 100644 --- a/ra_aid/agents/__init__.py +++ b/ra_aid/agents/__init__.py @@ -3,12 +3,14 @@ Agent package for various specialized agents. This package contains agents responsible for specific tasks such as cleaning up key facts and key snippets in the database when they -exceed certain thresholds, as well as performing research tasks. +exceed certain thresholds, as well as performing research tasks +and planning implementation. Includes agents for: - Key facts garbage collection - Key snippets garbage collection - Research tasks +- Planning tasks """ from typing import Optional @@ -16,10 +18,12 @@ from typing import Optional from ra_aid.agents.key_facts_gc_agent import run_key_facts_gc_agent from ra_aid.agents.key_snippets_gc_agent import run_key_snippets_gc_agent from ra_aid.agents.research_agent import run_research_agent, run_web_research_agent +from ra_aid.agents.planning_agent import run_planning_agent __all__ = [ "run_key_facts_gc_agent", "run_key_snippets_gc_agent", "run_research_agent", - "run_web_research_agent" + "run_web_research_agent", + "run_planning_agent" ] \ No newline at end of file diff --git a/ra_aid/agents/planning_agent.py b/ra_aid/agents/planning_agent.py new file mode 100644 index 0000000..9651d26 --- /dev/null +++ b/ra_aid/agents/planning_agent.py @@ -0,0 +1,361 @@ +""" +Planning agent implementation. + +This module provides functionality for running a planning agent to create implementation +plans. The agent can be configured with expert guidance and human-in-the-loop options. +""" + +import inspect +import os +import uuid +from datetime import datetime +from typing import Any, Optional + +from rich.console import Console +from rich.markdown import Markdown +from rich.panel import Panel + +from ra_aid.agent_context import agent_context, is_completed, reset_completion_flags, should_exit +# Import agent_utils functions at runtime to avoid circular imports +from ra_aid import agent_utils +from ra_aid.console.formatting import print_stage_header +from ra_aid.database.repositories.key_fact_repository import get_key_fact_repository +from ra_aid.database.repositories.key_snippet_repository import get_key_snippet_repository +from ra_aid.database.repositories.research_note_repository import get_research_note_repository +from ra_aid.database.repositories.config_repository import get_config_repository +from ra_aid.database.repositories.work_log_repository import get_work_log_repository +from ra_aid.env_inv_context import get_env_inv +from ra_aid.exceptions import AgentInterrupt +from ra_aid.llm import initialize_expert_llm +from ra_aid.logging_config import get_logger +from ra_aid.model_formatters import format_key_facts_dict +from ra_aid.model_formatters.key_snippets_formatter import format_key_snippets_dict +from ra_aid.model_formatters.research_notes_formatter import format_research_notes_dict +from ra_aid.models_params import models_params +from ra_aid.project_info import format_project_info, get_project_info +from ra_aid.prompts.expert_prompts import EXPERT_PROMPT_SECTION_PLANNING +from ra_aid.prompts.human_prompts import HUMAN_PROMPT_SECTION_PLANNING +from ra_aid.prompts.planning_prompts import PLANNING_PROMPT +from ra_aid.prompts.reasoning_assist_prompt import REASONING_ASSIST_PROMPT_PLANNING +from ra_aid.prompts.web_research_prompts import WEB_RESEARCH_PROMPT_SECTION_PLANNING +from ra_aid.tool_configs import get_planning_tools +from ra_aid.tools.memory import get_related_files, log_work_event + +logger = get_logger(__name__) +console = Console() + + +def run_planning_agent( + base_task: str, + model, + *, + expert_enabled: bool = False, + hil: bool = False, + memory: Optional[Any] = None, + thread_id: Optional[str] = None, +) -> Optional[str]: + """Run a planning agent to create implementation plans. + + Args: + base_task: The main task to plan implementation for + model: The LLM model to use + expert_enabled: Whether expert mode is enabled + hil: Whether human-in-the-loop mode is enabled + memory: Optional memory instance to use + thread_id: Optional thread ID (defaults to new UUID) + + Returns: + Optional[str]: The completion message if planning completed successfully + """ + thread_id = thread_id or str(uuid.uuid4()) + logger.debug("Starting planning agent with thread_id=%s", thread_id) + logger.debug("Planning configuration: expert=%s, hil=%s", expert_enabled, hil) + + if memory is None: + from langgraph.checkpoint.memory import MemorySaver + memory = MemorySaver() + + if thread_id is None: + thread_id = str(uuid.uuid4()) + + # Get latest project info + try: + project_info = get_project_info(".") + formatted_project_info = format_project_info(project_info) + except Exception as e: + logger.warning("Failed to get project info: %s", str(e)) + formatted_project_info = "Project info unavailable" + + tools = get_planning_tools( + expert_enabled=expert_enabled, + web_research_enabled=get_config_repository().get("web_research_enabled", False), + ) + + # Get model configuration + provider = get_config_repository().get("provider", "") + model_name = get_config_repository().get("model", "") + logger.debug("Checking for reasoning_assist_default on %s/%s", provider, model_name) + + # Get model configuration to check for reasoning_assist_default + model_config = {} + provider_models = models_params.get(provider, {}) + if provider_models and model_name in provider_models: + model_config = provider_models[model_name] + + # Check if reasoning assist is explicitly enabled/disabled + force_assistance = get_config_repository().get("force_reasoning_assistance", False) + disable_assistance = get_config_repository().get( + "disable_reasoning_assistance", False + ) + + if force_assistance: + reasoning_assist_enabled = True + elif disable_assistance: + reasoning_assist_enabled = False + else: + # Fall back to model default + reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) + + logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) + + # Get all the context information (used both for normal planning and reasoning assist) + current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + working_directory = os.getcwd() + + # Make sure key_facts is defined before using it + try: + key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) + except RuntimeError as e: + logger.error(f"Failed to access key fact repository: {str(e)}") + key_facts = "" + + # Make sure key_snippets is defined before using it + try: + key_snippets = format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ) + except RuntimeError as e: + logger.error(f"Failed to access key snippet repository: {str(e)}") + key_snippets = "" + + # Get formatted research notes using repository + try: + repository = get_research_note_repository() + notes_dict = repository.get_notes_dict() + formatted_research_notes = format_research_notes_dict(notes_dict) + except RuntimeError as e: + logger.error(f"Failed to access research note repository: {str(e)}") + formatted_research_notes = "" + + # Get related files + related_files = "\n".join(get_related_files()) + + # Get environment inventory information + env_inv = get_env_inv() + + # Display the planning stage header before any reasoning assistance + print_stage_header("Planning Stage") + + # Initialize expert guidance section + expert_guidance = "" + + # If reasoning assist is enabled, make a one-off call to the expert model + if reasoning_assist_enabled: + try: + logger.info( + "Reasoning assist enabled for model %s, getting expert guidance", + model_name, + ) + + # Collect tool descriptions + tool_metadata = [] + from ra_aid.tools.reflection import get_function_info as get_tool_info + + for tool in tools: + try: + tool_info = get_tool_info(tool.func) + name = tool.func.__name__ + description = inspect.getdoc(tool.func) + tool_metadata.append(f"Tool: {name}\nDescription: {description}\n") + except Exception as e: + logger.warning(f"Error getting tool info for {tool}: {e}") + + # Format tool metadata + formatted_tool_metadata = "\n".join(tool_metadata) + + # Initialize expert model + expert_model = initialize_expert_llm(provider, model_name) + + # Format the reasoning assist prompt + reasoning_assist_prompt = REASONING_ASSIST_PROMPT_PLANNING.format( + current_date=current_date, + working_directory=working_directory, + base_task=base_task, + key_facts=key_facts, + key_snippets=key_snippets, + research_notes=formatted_research_notes, + related_files=related_files, + env_inv=env_inv, + tool_metadata=formatted_tool_metadata, + ) + + # Show the reasoning assist query in a panel + console.print( + Panel( + Markdown( + "Consulting with the reasoning model on the best way to do this." + ), + title="📝 Thinking about the plan...", + border_style="yellow", + ) + ) + + logger.debug("Invoking expert model for reasoning assist") + # Make the call to the expert model + response = expert_model.invoke(reasoning_assist_prompt) + + # Check if the model supports think tags + supports_think_tag = model_config.get("supports_think_tag", False) + supports_thinking = model_config.get("supports_thinking", False) + + # Get response content, handling if it's a list (for Claude thinking mode) + content = None + + if hasattr(response, "content"): + content = response.content + else: + # Fallback if content attribute is missing + content = str(response) + + # Process content based on its type + if isinstance(content, list): + # Handle structured thinking mode (e.g., Claude 3.7) + thinking_content = None + response_text = None + + # Process each item in the list + for item in content: + if isinstance(item, dict): + # Extract thinking content + if item.get("type") == "thinking" and "thinking" in item: + thinking_content = item["thinking"] + logger.debug("Found structured thinking content") + # Extract response text + elif item.get("type") == "text" and "text" in item: + response_text = item["text"] + logger.debug("Found structured response text") + + # Display thinking content in a separate panel if available + if thinking_content and get_config_repository().get( + "show_thoughts", False + ): + logger.debug( + f"Displaying structured thinking content ({len(thinking_content)} chars)" + ) + console.print( + Panel( + Markdown(thinking_content), + title="💭 Expert Thinking", + border_style="yellow", + ) + ) + + # Use response_text if available, otherwise fall back to joining + if response_text: + content = response_text + else: + # Fallback: join list items if structured extraction failed + logger.debug( + "No structured response text found, joining list items" + ) + content = "\n".join(str(item) for item in content) + elif supports_think_tag or supports_thinking: + # Process thinking content using the centralized function + content, _ = agent_utils.process_thinking_content( + content=content, + supports_think_tag=supports_think_tag, + supports_thinking=supports_thinking, + panel_title="💭 Expert Thinking", + panel_style="yellow", + logger=logger, + ) + + # Display the expert guidance in a panel + console.print( + Panel( + Markdown(content), title="Reasoning Guidance", border_style="blue" + ) + ) + + # Use the content as expert guidance + expert_guidance = ( + content + "\n\nCONSULT WITH THE EXPERT FREQUENTLY ON THIS TASK" + ) + + logger.info("Received expert guidance for planning") + except Exception as e: + logger.error("Error getting expert guidance for planning: %s", e) + expert_guidance = "" + + agent = agent_utils.create_agent(model, tools, checkpointer=memory, agent_type="planner") + + expert_section = EXPERT_PROMPT_SECTION_PLANNING if expert_enabled else "" + human_section = HUMAN_PROMPT_SECTION_PLANNING if hil else "" + web_research_section = ( + WEB_RESEARCH_PROMPT_SECTION_PLANNING + if get_config_repository().get("web_research_enabled", False) + else "" + ) + + # Prepare expert guidance section if expert guidance is available + expert_guidance_section = "" + if expert_guidance: + expert_guidance_section = f""" +{expert_guidance} +""" + + planning_prompt = PLANNING_PROMPT.format( + current_date=current_date, + working_directory=working_directory, + expert_section=expert_section, + human_section=human_section, + web_research_section=web_research_section, + base_task=base_task, + project_info=formatted_project_info, + research_notes=formatted_research_notes, + related_files=related_files, + key_facts=key_facts, + key_snippets=key_snippets, + work_log=get_work_log_repository().format_work_log(), + research_only_note=( + "" + if get_config_repository().get("research_only", False) + else " Only request implementation if the user explicitly asked for changes to be made." + ), + env_inv=env_inv, + expert_guidance_section=expert_guidance_section, + ) + + config_values = get_config_repository().get_all() + recursion_limit = get_config_repository().get( + "recursion_limit", 100 + ) + run_config = { + "configurable": {"thread_id": thread_id}, + "recursion_limit": recursion_limit, + } + run_config.update(config_values) + + try: + logger.debug("Planning agent completed successfully") + none_or_fallback_handler = agent_utils.init_fallback_handler(agent, tools) + _result = agent_utils.run_agent_with_retry(agent, planning_prompt, none_or_fallback_handler) + if _result: + # Log planning completion + log_work_event(f"Completed planning phase for: {base_task}") + return _result + except (KeyboardInterrupt, AgentInterrupt): + raise + except Exception as e: + logger.error("Planning agent failed: %s", str(e), exc_info=True) + raise \ No newline at end of file diff --git a/ra_aid/tools/agent.py b/ra_aid/tools/agent.py index eabcbf7..728cb34 100644 --- a/ra_aid/tools/agent.py +++ b/ra_aid/tools/agent.py @@ -481,7 +481,7 @@ def request_implementation(task_spec: str) -> str: try: # Run planning agent - from ..agent_utils import run_planning_agent + from ..agents import run_planning_agent reset_completion_flags() diff --git a/tests/ra_aid/test_main.py b/tests/ra_aid/test_main.py index 6674bf7..5bf086d 100644 --- a/tests/ra_aid/test_main.py +++ b/tests/ra_aid/test_main.py @@ -54,7 +54,7 @@ def mock_dependencies(monkeypatch): monkeypatch.setattr("ra_aid.__main__.create_agent", lambda *args, **kwargs: None) monkeypatch.setattr("ra_aid.__main__.run_agent_with_retry", lambda *args, **kwargs: None) monkeypatch.setattr("ra_aid.__main__.run_research_agent", lambda *args, **kwargs: None) - monkeypatch.setattr("ra_aid.__main__.run_planning_agent", lambda *args, **kwargs: None) + monkeypatch.setattr("ra_aid.agents.planning_agent.run_planning_agent", lambda *args, **kwargs: None) # Mock LLM initialization def mock_config_update(*args, **kwargs): @@ -268,7 +268,7 @@ def test_temperature_validation(mock_dependencies, mock_config_repository): with patch("ra_aid.__main__.initialize_llm", return_value=None) as mock_init_llm: # Also patch any calls that would actually use the mocked initialize_llm function with patch("ra_aid.__main__.run_research_agent", return_value=None): - with patch("ra_aid.__main__.run_planning_agent", return_value=None): + with patch("ra_aid.agents.planning_agent.run_planning_agent", return_value=None): with patch.object( sys, "argv", ["ra-aid", "-m", "test", "--temperature", "0.7"] ): diff --git a/tests/ra_aid/tools/test_agent.py b/tests/ra_aid/tools/test_agent.py index a8d8f1c..a7b0d6b 100644 --- a/tests/ra_aid/tools/test_agent.py +++ b/tests/ra_aid/tools/test_agent.py @@ -217,7 +217,7 @@ def test_request_research_and_implementation_uses_key_fact_repository(reset_memo def test_request_implementation_uses_key_fact_repository(reset_memory, mock_functions): """Test that request_implementation uses KeyFactRepository correctly.""" # Mock running the planning agent - with patch('ra_aid.agent_utils.run_planning_agent'): + with patch('ra_aid.agents.planning_agent.run_planning_agent'): # Call the function result = request_implementation("test task") From 51fa86b5c4f6d2ff6c894a2c0951611007927114 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 14:44:34 -0400 Subject: [PATCH 05/14] refactor: extract run_task_implementation_agent --- ra_aid/agent_utils.py | 268 +--------------------- ra_aid/agents/__init__.py | 15 +- ra_aid/agents/implementation_agent.py | 316 ++++++++++++++++++++++++++ ra_aid/prompts/research_prompts.py | 1 - ra_aid/tools/agent.py | 2 +- tests/ra_aid/tools/test_agent.py | 2 +- 6 files changed, 328 insertions(+), 276 deletions(-) create mode 100644 ra_aid/agents/implementation_agent.py diff --git a/ra_aid/agent_utils.py b/ra_aid/agent_utils.py index 8d1d165..dec0883 100644 --- a/ra_aid/agent_utils.py +++ b/ra_aid/agent_utils.py @@ -364,273 +364,7 @@ def create_agent( from ra_aid.agents.research_agent import run_research_agent, run_web_research_agent - - -def run_task_implementation_agent( - base_task: str, - tasks: list, - task: str, - plan: str, - related_files: list, - model, - *, - expert_enabled: bool = False, - web_research_enabled: bool = False, - memory: Optional[Any] = None, - thread_id: Optional[str] = None, -) -> Optional[str]: - """Run an implementation agent for a specific task. - - Args: - base_task: The main task being implemented - tasks: List of tasks to implement - plan: The implementation plan - related_files: List of related files - model: The LLM model to use - expert_enabled: Whether expert mode is enabled - web_research_enabled: Whether web research is enabled - memory: Optional memory instance to use - thread_id: Optional thread ID (defaults to new UUID) - - Returns: - Optional[str]: The completion message if task completed successfully - """ - thread_id = thread_id or str(uuid.uuid4()) - logger.debug("Starting implementation agent with thread_id=%s", thread_id) - logger.debug( - "Implementation configuration: expert=%s, web=%s", - expert_enabled, - web_research_enabled, - ) - logger.debug("Task details: base_task=%s, current_task=%s", base_task, task) - logger.debug("Related files: %s", related_files) - - if memory is None: - memory = MemorySaver() - - if thread_id is None: - thread_id = str(uuid.uuid4()) - - tools = get_implementation_tools( - expert_enabled=expert_enabled, - web_research_enabled=get_config_repository().get("web_research_enabled", False), - ) - - agent = create_agent(model, tools, checkpointer=memory, agent_type="planner") - - current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - working_directory = os.getcwd() - - # Make sure key_facts is defined before using it - try: - key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) - except RuntimeError as e: - logger.error(f"Failed to access key fact repository: {str(e)}") - key_facts = "" - - # Get formatted research notes using repository - try: - repository = get_research_note_repository() - notes_dict = repository.get_notes_dict() - formatted_research_notes = format_research_notes_dict(notes_dict) - except RuntimeError as e: - logger.error(f"Failed to access research note repository: {str(e)}") - formatted_research_notes = "" - - # Get latest project info - try: - project_info = get_project_info(".") - formatted_project_info = format_project_info(project_info) - except Exception as e: - logger.warning("Failed to get project info: %s", str(e)) - formatted_project_info = "Project info unavailable" - - # Get environment inventory information - env_inv = get_env_inv() - - # Get model configuration to check for reasoning_assist_default - provider = get_config_repository().get("provider", "") - model_name = get_config_repository().get("model", "") - logger.debug("Checking for reasoning_assist_default on %s/%s", provider, model_name) - - model_config = {} - provider_models = models_params.get(provider, {}) - if provider_models and model_name in provider_models: - model_config = provider_models[model_name] - - # Check if reasoning assist is explicitly enabled/disabled - force_assistance = get_config_repository().get("force_reasoning_assistance", False) - disable_assistance = get_config_repository().get( - "disable_reasoning_assistance", False - ) - - if force_assistance: - reasoning_assist_enabled = True - elif disable_assistance: - reasoning_assist_enabled = False - else: - # Fall back to model default - reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) - - logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) - - # Initialize implementation guidance section - implementation_guidance_section = "" - - # If reasoning assist is enabled, make a one-off call to the expert model - if reasoning_assist_enabled: - try: - logger.info( - "Reasoning assist enabled for model %s, getting implementation guidance", - model_name, - ) - - # Collect tool descriptions - tool_metadata = [] - from ra_aid.tools.reflection import get_function_info as get_tool_info - - for tool in tools: - try: - tool_info = get_tool_info(tool.func) - name = tool.func.__name__ - description = inspect.getdoc(tool.func) - tool_metadata.append( - f"Tool: {name}\\nDescription: {description}\\n" - ) - except Exception as e: - logger.warning(f"Error getting tool info for {tool}: {e}") - - # Format tool metadata - formatted_tool_metadata = "\\n".join(tool_metadata) - - # Initialize expert model - expert_model = initialize_expert_llm(provider, model_name) - - # Format the reasoning assist prompt for implementation - reasoning_assist_prompt = REASONING_ASSIST_PROMPT_IMPLEMENTATION.format( - current_date=current_date, - working_directory=working_directory, - task=task, - key_facts=key_facts, - key_snippets=format_key_snippets_dict( - get_key_snippet_repository().get_snippets_dict() - ), - research_notes=formatted_research_notes, - related_files="\\n".join(related_files), - env_inv=env_inv, - tool_metadata=formatted_tool_metadata, - ) - - # Show the reasoning assist query in a panel - console.print( - Panel( - Markdown( - "Consulting with the reasoning model on the best implementation approach." - ), - title="📝 Thinking about implementation...", - border_style="yellow", - ) - ) - - logger.debug("Invoking expert model for implementation reasoning assist") - # Make the call to the expert model - response = expert_model.invoke(reasoning_assist_prompt) - - # Check if the model supports think tags - supports_think_tag = model_config.get("supports_think_tag", False) - supports_thinking = model_config.get("supports_thinking", False) - - # Process response content - content = None - - if hasattr(response, "content"): - content = response.content - else: - # Fallback if content attribute is missing - content = str(response) - - # Process the response content using the centralized function - content, extracted_thinking = process_thinking_content( - content=content, - supports_think_tag=supports_think_tag, - supports_thinking=supports_thinking, - panel_title="💭 Implementation Thinking", - panel_style="yellow", - logger=logger, - ) - - # Display the implementation guidance in a panel - console.print( - Panel( - Markdown(content), - title="Implementation Guidance", - border_style="blue", - ) - ) - - # Format the implementation guidance section for the prompt - implementation_guidance_section = f""" -{content} -""" - - logger.info("Received implementation guidance") - except Exception as e: - logger.error("Error getting implementation guidance: %s", e) - implementation_guidance_section = "" - - prompt = IMPLEMENTATION_PROMPT.format( - current_date=current_date, - working_directory=working_directory, - base_task=base_task, - task=task, - tasks=tasks, - plan=plan, - related_files=related_files, - key_facts=key_facts, - key_snippets=format_key_snippets_dict( - get_key_snippet_repository().get_snippets_dict() - ), - research_notes=formatted_research_notes, - work_log=get_work_log_repository().format_work_log(), - expert_section=EXPERT_PROMPT_SECTION_IMPLEMENTATION if expert_enabled else "", - human_section=( - HUMAN_PROMPT_SECTION_IMPLEMENTATION - if get_config_repository().get("hil", False) - else "" - ), - web_research_section=( - WEB_RESEARCH_PROMPT_SECTION_CHAT - if get_config_repository().get("web_research_enabled", False) - else "" - ), - env_inv=env_inv, - project_info=formatted_project_info, - implementation_guidance_section=implementation_guidance_section, - ) - - config_values = get_config_repository().get_all() - recursion_limit = get_config_repository().get( - "recursion_limit", DEFAULT_RECURSION_LIMIT - ) - run_config = { - "configurable": {"thread_id": thread_id}, - "recursion_limit": recursion_limit, - } - run_config.update(config_values) - - try: - logger.debug("Implementation agent completed successfully") - none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry(agent, prompt, none_or_fallback_handler) - if _result: - # Log task implementation completion - log_work_event(f"Completed implementation of task: {task}") - return _result - except (KeyboardInterrupt, AgentInterrupt): - raise - except Exception as e: - logger.error("Implementation agent failed: %s", str(e), exc_info=True) - raise +from ra_aid.agents.implementation_agent import run_task_implementation_agent _CONTEXT_STACK = [] diff --git a/ra_aid/agents/__init__.py b/ra_aid/agents/__init__.py index ecf2f8a..59c5a53 100644 --- a/ra_aid/agents/__init__.py +++ b/ra_aid/agents/__init__.py @@ -3,27 +3,30 @@ Agent package for various specialized agents. This package contains agents responsible for specific tasks such as cleaning up key facts and key snippets in the database when they -exceed certain thresholds, as well as performing research tasks -and planning implementation. +exceed certain thresholds, as well as performing research tasks, +planning implementation, and implementing specific tasks. Includes agents for: - Key facts garbage collection - Key snippets garbage collection -- Research tasks +- Implementation tasks - Planning tasks +- Research tasks """ from typing import Optional +from ra_aid.agents.implementation_agent import run_task_implementation_agent from ra_aid.agents.key_facts_gc_agent import run_key_facts_gc_agent from ra_aid.agents.key_snippets_gc_agent import run_key_snippets_gc_agent -from ra_aid.agents.research_agent import run_research_agent, run_web_research_agent from ra_aid.agents.planning_agent import run_planning_agent +from ra_aid.agents.research_agent import run_research_agent, run_web_research_agent __all__ = [ "run_key_facts_gc_agent", "run_key_snippets_gc_agent", + "run_planning_agent", "run_research_agent", - "run_web_research_agent", - "run_planning_agent" + "run_task_implementation_agent", + "run_web_research_agent" ] \ No newline at end of file diff --git a/ra_aid/agents/implementation_agent.py b/ra_aid/agents/implementation_agent.py new file mode 100644 index 0000000..f0bffff --- /dev/null +++ b/ra_aid/agents/implementation_agent.py @@ -0,0 +1,316 @@ +""" +Implementation agent for executing specific implementation tasks. + +This module provides functionality for running a task implementation agent +to execute specific tasks based on a plan. The agent can be configured with +expert guidance and web research options. +""" + +import inspect +import os +import uuid +from datetime import datetime +from typing import Any, Optional, List + +from rich.console import Console +from rich.markdown import Markdown +from rich.panel import Panel + +from ra_aid.agent_context import agent_context, is_completed, reset_completion_flags, should_exit +# Import agent_utils functions at runtime to avoid circular imports +from ra_aid import agent_utils +from ra_aid.database.repositories.key_fact_repository import get_key_fact_repository +from ra_aid.database.repositories.key_snippet_repository import get_key_snippet_repository +from ra_aid.database.repositories.human_input_repository import get_human_input_repository +from ra_aid.database.repositories.research_note_repository import get_research_note_repository +from ra_aid.database.repositories.config_repository import get_config_repository +from ra_aid.database.repositories.work_log_repository import get_work_log_repository +from ra_aid.env_inv_context import get_env_inv +from ra_aid.exceptions import AgentInterrupt +from ra_aid.llm import initialize_expert_llm +from ra_aid.logging_config import get_logger +from ra_aid.model_formatters import format_key_facts_dict +from ra_aid.model_formatters.key_snippets_formatter import format_key_snippets_dict +from ra_aid.model_formatters.research_notes_formatter import format_research_notes_dict +from ra_aid.models_params import models_params, DEFAULT_TOKEN_LIMIT +from ra_aid.project_info import format_project_info, get_project_info +from ra_aid.prompts.expert_prompts import EXPERT_PROMPT_SECTION_IMPLEMENTATION +from ra_aid.prompts.human_prompts import HUMAN_PROMPT_SECTION_IMPLEMENTATION +from ra_aid.prompts.implementation_prompts import IMPLEMENTATION_PROMPT +from ra_aid.prompts.reasoning_assist_prompt import REASONING_ASSIST_PROMPT_IMPLEMENTATION +from ra_aid.prompts.web_research_prompts import WEB_RESEARCH_PROMPT_SECTION_CHAT +from ra_aid.tool_configs import get_implementation_tools +from ra_aid.tools.memory import get_related_files, log_work_event +from ra_aid.text.processing import process_thinking_content + +logger = get_logger(__name__) +console = Console() + + +def run_task_implementation_agent( + base_task: str, + tasks: list, + task: str, + plan: str, + related_files: list, + model, + *, + expert_enabled: bool = False, + web_research_enabled: bool = False, + memory: Optional[Any] = None, + thread_id: Optional[str] = None, +) -> Optional[str]: + """Run an implementation agent for a specific task. + + Args: + base_task: The main task being implemented + tasks: List of tasks to implement + task: The current task to implement + plan: The implementation plan + related_files: List of related files + model: The LLM model to use + expert_enabled: Whether expert mode is enabled + web_research_enabled: Whether web research is enabled + memory: Optional memory instance to use + thread_id: Optional thread ID (defaults to new UUID) + + Returns: + Optional[str]: The completion message if task completed successfully + """ + thread_id = thread_id or str(uuid.uuid4()) + logger.debug("Starting implementation agent with thread_id=%s", thread_id) + logger.debug( + "Implementation configuration: expert=%s, web=%s", + expert_enabled, + web_research_enabled, + ) + logger.debug("Task details: base_task=%s, current_task=%s", base_task, task) + logger.debug("Related files: %s", related_files) + + if memory is None: + from langgraph.checkpoint.memory import MemorySaver + memory = MemorySaver() + + if thread_id is None: + thread_id = str(uuid.uuid4()) + + tools = get_implementation_tools( + expert_enabled=expert_enabled, + web_research_enabled=get_config_repository().get("web_research_enabled", False), + ) + + agent = agent_utils.create_agent(model, tools, checkpointer=memory, agent_type="planner") + + current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + working_directory = os.getcwd() + + # Make sure key_facts is defined before using it + try: + key_facts = format_key_facts_dict(get_key_fact_repository().get_facts_dict()) + except RuntimeError as e: + logger.error(f"Failed to access key fact repository: {str(e)}") + key_facts = "" + + # Get formatted research notes using repository + try: + repository = get_research_note_repository() + notes_dict = repository.get_notes_dict() + formatted_research_notes = format_research_notes_dict(notes_dict) + except RuntimeError as e: + logger.error(f"Failed to access research note repository: {str(e)}") + formatted_research_notes = "" + + # Get latest project info + try: + project_info = get_project_info(".") + formatted_project_info = format_project_info(project_info) + except Exception as e: + logger.warning("Failed to get project info: %s", str(e)) + formatted_project_info = "Project info unavailable" + + # Get environment inventory information + env_inv = get_env_inv() + + # Get model configuration to check for reasoning_assist_default + provider = get_config_repository().get("provider", "") + model_name = get_config_repository().get("model", "") + logger.debug("Checking for reasoning_assist_default on %s/%s", provider, model_name) + + model_config = {} + provider_models = models_params.get(provider, {}) + if provider_models and model_name in provider_models: + model_config = provider_models[model_name] + + # Check if reasoning assist is explicitly enabled/disabled + force_assistance = get_config_repository().get("force_reasoning_assistance", False) + disable_assistance = get_config_repository().get( + "disable_reasoning_assistance", False + ) + + if force_assistance: + reasoning_assist_enabled = True + elif disable_assistance: + reasoning_assist_enabled = False + else: + # Fall back to model default + reasoning_assist_enabled = model_config.get("reasoning_assist_default", False) + + logger.debug("Reasoning assist enabled: %s", reasoning_assist_enabled) + + # Initialize implementation guidance section + implementation_guidance_section = "" + + # If reasoning assist is enabled, make a one-off call to the expert model + if reasoning_assist_enabled: + try: + logger.info( + "Reasoning assist enabled for model %s, getting implementation guidance", + model_name, + ) + + # Collect tool descriptions + tool_metadata = [] + from ra_aid.tools.reflection import get_function_info as get_tool_info + + for tool in tools: + try: + tool_info = get_tool_info(tool.func) + name = tool.func.__name__ + description = inspect.getdoc(tool.func) + tool_metadata.append( + f"Tool: {name}\nDescription: {description}\n" + ) + except Exception as e: + logger.warning(f"Error getting tool info for {tool}: {e}") + + # Format tool metadata + formatted_tool_metadata = "\n".join(tool_metadata) + + # Initialize expert model + expert_model = initialize_expert_llm(provider, model_name) + + # Format the reasoning assist prompt for implementation + reasoning_assist_prompt = REASONING_ASSIST_PROMPT_IMPLEMENTATION.format( + current_date=current_date, + working_directory=working_directory, + task=task, + key_facts=key_facts, + key_snippets=format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ), + research_notes=formatted_research_notes, + related_files="\n".join(related_files), + env_inv=env_inv, + tool_metadata=formatted_tool_metadata, + ) + + # Show the reasoning assist query in a panel + console.print( + Panel( + Markdown( + "Consulting with the reasoning model on the best implementation approach." + ), + title="📝 Thinking about implementation...", + border_style="yellow", + ) + ) + + logger.debug("Invoking expert model for implementation reasoning assist") + # Make the call to the expert model + response = expert_model.invoke(reasoning_assist_prompt) + + # Check if the model supports think tags + supports_think_tag = model_config.get("supports_think_tag", False) + supports_thinking = model_config.get("supports_thinking", False) + + # Process response content + content = None + + if hasattr(response, "content"): + content = response.content + else: + # Fallback if content attribute is missing + content = str(response) + + # Process the response content using the centralized function + content, extracted_thinking = process_thinking_content( + content=content, + supports_think_tag=supports_think_tag, + supports_thinking=supports_thinking, + panel_title="💭 Implementation Thinking", + panel_style="yellow", + logger=logger, + ) + + # Display the implementation guidance in a panel + console.print( + Panel( + Markdown(content), + title="Implementation Guidance", + border_style="blue", + ) + ) + + # Format the implementation guidance section for the prompt + implementation_guidance_section = f""" +{content} +""" + + logger.info("Received implementation guidance") + except Exception as e: + logger.error("Error getting implementation guidance: %s", e) + implementation_guidance_section = "" + + prompt = IMPLEMENTATION_PROMPT.format( + current_date=current_date, + working_directory=working_directory, + base_task=base_task, + task=task, + tasks=tasks, + plan=plan, + related_files=related_files, + key_facts=key_facts, + key_snippets=format_key_snippets_dict( + get_key_snippet_repository().get_snippets_dict() + ), + research_notes=formatted_research_notes, + work_log=get_work_log_repository().format_work_log(), + expert_section=EXPERT_PROMPT_SECTION_IMPLEMENTATION if expert_enabled else "", + human_section=( + HUMAN_PROMPT_SECTION_IMPLEMENTATION + if get_config_repository().get("hil", False) + else "" + ), + web_research_section=( + WEB_RESEARCH_PROMPT_SECTION_CHAT + if get_config_repository().get("web_research_enabled", False) + else "" + ), + env_inv=env_inv, + project_info=formatted_project_info, + implementation_guidance_section=implementation_guidance_section, + ) + + config_values = get_config_repository().get_all() + recursion_limit = get_config_repository().get( + "recursion_limit", 100 + ) + run_config = { + "configurable": {"thread_id": thread_id}, + "recursion_limit": recursion_limit, + } + run_config.update(config_values) + + try: + logger.debug("Implementation agent completed successfully") + none_or_fallback_handler = agent_utils.init_fallback_handler(agent, tools) + _result = agent_utils.run_agent_with_retry(agent, prompt, none_or_fallback_handler) + if _result: + # Log task implementation completion + log_work_event(f"Completed implementation of task: {task}") + return _result + except (KeyboardInterrupt, AgentInterrupt): + raise + except Exception as e: + logger.error("Implementation agent failed: %s", str(e), exc_info=True) + raise \ No newline at end of file diff --git a/ra_aid/prompts/research_prompts.py b/ra_aid/prompts/research_prompts.py index 2d63a31..8388c12 100644 --- a/ra_aid/prompts/research_prompts.py +++ b/ra_aid/prompts/research_prompts.py @@ -66,7 +66,6 @@ You must: Do so by incrementally and systematically exploring the filesystem with careful directory listing tool calls. You can use fuzzy file search to quickly find relevant files matching a search pattern. Use ripgrep_search extensively to do *exhaustive* searches for all references to anything that might be changed as part of the base level task. - Prefer to use ripgrep_search with context params rather than reading whole files in order to preserve context tokens. Call emit_key_facts and emit_key_snippet on key information/facts/snippets of code you discover about this project during your research. This is information you will be writing down to be able to efficiently complete work in the future, so be on the lookout for these and make it count. While it is important to emit key facts and snippets, only emit ones that are truly important info about the project or this task. Do not excessively emit key facts or snippets. Be strategic about it. diff --git a/ra_aid/tools/agent.py b/ra_aid/tools/agent.py index 728cb34..26190e3 100644 --- a/ra_aid/tools/agent.py +++ b/ra_aid/tools/agent.py @@ -347,7 +347,7 @@ def request_task_implementation(task_spec: str) -> str: try: print_task_header(task_spec) # Run implementation agent - from ..agent_utils import run_task_implementation_agent + from ..agents.implementation_agent import run_task_implementation_agent reset_completion_flags() diff --git a/tests/ra_aid/tools/test_agent.py b/tests/ra_aid/tools/test_agent.py index a7b0d6b..7ee0226 100644 --- a/tests/ra_aid/tools/test_agent.py +++ b/tests/ra_aid/tools/test_agent.py @@ -237,7 +237,7 @@ def test_request_implementation_uses_key_fact_repository(reset_memory, mock_func def test_request_task_implementation_uses_key_fact_repository(reset_memory, mock_functions): """Test that request_task_implementation uses KeyFactRepository correctly.""" # Mock running the implementation agent - with patch('ra_aid.agent_utils.run_task_implementation_agent'): + with patch('ra_aid.agents.implementation_agent.run_task_implementation_agent'): # Call the function result = request_task_implementation("test task") From c98c107ce3eab1255d81c69a36610acc97f040e8 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 14:58:47 -0400 Subject: [PATCH 06/14] use expert model for reasoning assisted mode --- ra_aid/agents/implementation_agent.py | 4 ++-- ra_aid/agents/planning_agent.py | 4 ++-- ra_aid/agents/research_agent.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ra_aid/agents/implementation_agent.py b/ra_aid/agents/implementation_agent.py index f0bffff..352e241 100644 --- a/ra_aid/agents/implementation_agent.py +++ b/ra_aid/agents/implementation_agent.py @@ -132,8 +132,8 @@ def run_task_implementation_agent( env_inv = get_env_inv() # Get model configuration to check for reasoning_assist_default - provider = get_config_repository().get("provider", "") - model_name = get_config_repository().get("model", "") + provider = get_config_repository().get("expert_provider", "") + model_name = get_config_repository().get("expert_model", "") logger.debug("Checking for reasoning_assist_default on %s/%s", provider, model_name) model_config = {} diff --git a/ra_aid/agents/planning_agent.py b/ra_aid/agents/planning_agent.py index 9651d26..e6dd8e8 100644 --- a/ra_aid/agents/planning_agent.py +++ b/ra_aid/agents/planning_agent.py @@ -92,8 +92,8 @@ def run_planning_agent( ) # Get model configuration - provider = get_config_repository().get("provider", "") - model_name = get_config_repository().get("model", "") + provider = get_config_repository().get("expert_provider", "") + model_name = get_config_repository().get("expert_model", "") logger.debug("Checking for reasoning_assist_default on %s/%s", provider, model_name) # Get model configuration to check for reasoning_assist_default diff --git a/ra_aid/agents/research_agent.py b/ra_aid/agents/research_agent.py index 61fbc05..b5903a7 100644 --- a/ra_aid/agents/research_agent.py +++ b/ra_aid/agents/research_agent.py @@ -144,8 +144,8 @@ def run_research_agent( ) # Get model info for reasoning assistance configuration - provider = get_config_repository().get("provider", "") - model_name = get_config_repository().get("model", "") + provider = get_config_repository().get("expert_provider", "") + model_name = get_config_repository().get("expert_model", "") # Get model configuration to check for reasoning_assist_default model_config = {} From e81421a95aff68e70f8d9a21c8621337ff52c86d Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 15:30:22 -0400 Subject: [PATCH 07/14] improve prompts --- ra_aid/agents/research_agent.py | 4 ++- ra_aid/prompts/reasoning_assist_prompt.py | 30 +++++++++++++++++++++++ ra_aid/prompts/research_prompts.py | 9 +++---- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/ra_aid/agents/research_agent.py b/ra_aid/agents/research_agent.py index b5903a7..667f07b 100644 --- a/ra_aid/agents/research_agent.py +++ b/ra_aid/agents/research_agent.py @@ -333,7 +333,9 @@ def run_research_agent( if expert_guidance: expert_guidance_section = f""" {expert_guidance} -""" + +YOU MUST FOLLOW THE EXPERT'S GUIDANCE OR ELSE BE TERMINATED! +""" # Format research notes if available # We get research notes earlier for reasoning assistance diff --git a/ra_aid/prompts/reasoning_assist_prompt.py b/ra_aid/prompts/reasoning_assist_prompt.py index 075dad5..77a4bea 100644 --- a/ra_aid/prompts/reasoning_assist_prompt.py +++ b/ra_aid/prompts/reasoning_assist_prompt.py @@ -48,6 +48,19 @@ Given the available information, tools, and base task, write a couple paragraphs The agent has a tendency to do the same work/functin calls over and over again. Answer quickly and confidently with five sentences at most. + +DO NOT WRITE CODE +WRITE AT LEAST ONE SENTENCE +WRITE NO MORE THAN FIVE PARAGRAPHS. +WRITE ABOUT HOW THE AGENT WILL USE THE TOOLS AVAILABLE TO EFFICIENTLY ACCOMPLISH THE GOAL. +REFERENCE ACTUAL TOOL NAMES IN YOUR WRITING, BUT KEEP THE WRITING PLAIN LOGICAL ENGLISH. +BE DETAILED AND INCLUDE LOGIC BRANCHES FOR WHAT TO DO IF DIFFERENT TOOLS RETURN DIFFERENT THINGS. +THINK OF IT AS A FLOW CHART BUT IN NATURAL ENGLISH. +REMEMBER THE ULTIMATE GOAL AT THIS STAGE IS TO BREAK THINGS DOWN INTO DISCRETE TASKS AND CALL request_task_implementation FOR EACH TASK. +PROPOSE THE TASK BREAKDOWN TO THE AGENT. INCLUDE THIS AS A BULLETED LIST IN YOUR GUIDANCE. +WE ARE NOT WRITING ANY CODE AT THIS STAGE. +THE AGENT IS VERY FORGETFUL AND YOUR WRITING MUST INCLUDE REMARKS ABOUT HOW IT SHOULD USE *ALL* AVAILABLE TOOLS, INCLUDING AND ESPECIALLY ask_expert. +THE AGENT IS DUMB AND NEEDS REALLY DETAILED GUIDANCE LIKE LITERALLY REMINDING IT TO CALL request_task_implementation FOR EACH TASK IN YOUR BULLETED LIST. """ REASONING_ASSIST_PROMPT_IMPLEMENTATION = """Current Date: {current_date} @@ -93,6 +106,14 @@ IF ANYTHING AT ALL GOES WRONG, CALL ask_expert. Given the available information, tools, and base task, write a couple paragraphs about how an agentic system might use the available tools to implement the given task definition. The agent will be writing code and making changes at this point. Answer quickly and confidently with a few sentences at most. + +WRITE AT LEAST ONE SENTENCE +WRITE NO MORE THAN FIVE PARAGRAPHS. +WRITE ABOUT HOW THE AGENT WILL USE THE TOOLS AVAILABLE TO EFFICIENTLY ACCOMPLISH THE GOAL. +REFERENCE ACTUAL TOOL NAMES IN YOUR WRITING, BUT KEEP THE WRITING PLAIN LOGICAL ENGLISH. +BE DETAILED AND INCLUDE LOGIC BRANCHES FOR WHAT TO DO IF DIFFERENT TOOLS RETURN DIFFERENT THINGS. +THE AGENT IS VERY FORGETFUL AND YOUR WRITING MUST INCLUDE REMARKS ABOUT HOW IT SHOULD USE *ALL* AVAILABLE TOOLS, INCLUDING AND ESPECIALLY ask_expert. +THINK OF IT AS A FLOW CHART BUT IN NATURAL ENGLISH. """ REASONING_ASSIST_PROMPT_RESEARCH = """Current Date: {current_date} @@ -136,4 +157,13 @@ IF INFORMATION IS TOO COMPLEX TO UNDERSTAND, THE AGENT SHOULD USE ask_expert. Given the available information, tools, and base task or query, write a couple paragraphs about how an agentic system might use the available tools to research the codebase, identify important components, gather key information, and emit key facts and snippets. The focus is on thorough investigation and understanding before any implementation. Remember, the research agent generally should emit research notes at the end of its execution, right before it calls request_implementation if a change or new work is required. Answer quickly and confidently with five sentences at most. + +DO NOT WRITE CODE +WRITE AT LEAST ONE SENTENCE +WRITE NO MORE THAN FIVE PARAGRAPHS. +WRITE ABOUT HOW THE AGENT WILL USE THE TOOLS AVAILABLE TO EFFICIENTLY ACCOMPLISH THE GOAL. +REFERENCE ACTUAL TOOL NAMES IN YOUR WRITING, BUT KEEP THE WRITING PLAIN LOGICAL ENGLISH. +BE DETAILED AND INCLUDE LOGIC BRANCHES FOR WHAT TO DO IF DIFFERENT TOOLS RETURN DIFFERENT THINGS. +THINK OF IT AS A FLOW CHART BUT IN NATURAL ENGLISH. +THE AGENT IS VERY FORGETFUL AND YOUR WRITING MUST INCLUDE REMARKS ABOUT HOW IT SHOULD USE *ALL* AVAILABLE TOOLS, INCLUDING AND ESPECIALLY ask_expert. """ diff --git a/ra_aid/prompts/research_prompts.py b/ra_aid/prompts/research_prompts.py index 8388c12..b458b4c 100644 --- a/ra_aid/prompts/research_prompts.py +++ b/ra_aid/prompts/research_prompts.py @@ -123,7 +123,6 @@ If you find this is an empty directory, you can stop research immediately and as {expert_section} {human_section} {web_research_section} -{expert_guidance_section} You have often been criticized for: - Needlessly requesting more research tasks, especially for general background knowledge which you already know. @@ -180,12 +179,12 @@ If this is a top-level README.md or docs folder, start there. If the user explicitly requests implementation, that means you should first perform all the background research for that task, then call request_implementation where the implementation will be carried out. +{expert_guidance_section} + {base_task} -{expert_guidance_section} - USER QUERY *ALWAYS* TAKES PRECEDENCE OVER EVERYTHING IN PREVIOUS RESEARCH. KEEP IT SIMPLE @@ -207,12 +206,12 @@ You have been spawned by a higher level research agent, so only spawn more resea When you emit research notes, keep it extremely concise and relevant only to the specific research subquery you've been assigned. +{expert_guidance_section} + {base_task} -{expert_guidance_section} - USER QUERY *ALWAYS* TAKES PRECEDENCE OVER EVERYTHING IN PREVIOUS RESEARCH. KEEP IT SIMPLE From 7d579f5557fc406b5d5ee9127bbde69daaae5a8c Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 15:37:38 -0400 Subject: [PATCH 08/14] improve prompts --- ra_aid/prompts/implementation_prompts.py | 4 ++-- ra_aid/prompts/planning_prompts.py | 4 ++-- ra_aid/prompts/reasoning_assist_prompt.py | 4 ++++ ra_aid/prompts/research_prompts.py | 8 ++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/ra_aid/prompts/implementation_prompts.py b/ra_aid/prompts/implementation_prompts.py index 333afb2..6268692 100644 --- a/ra_aid/prompts/implementation_prompts.py +++ b/ra_aid/prompts/implementation_prompts.py @@ -45,8 +45,6 @@ YOU MUST **EXPLICITLY** INCLUDE ANY PATHS FROM THE ABOVE INFO IF NEEDED. IT IS N READ AND STUDY ACTUAL LIBRARY HEADERS/CODE FROM THE ENVIRONMENT, IF AVAILABLE AND RELEVANT. -{implementation_guidance_section} - Important Notes: - Focus solely on the given task and implement it as described. - Scale the complexity of your solution to the complexity of the request. For simple requests, keep it straightforward and minimal. For complex requests, maintain the previously planned depth. @@ -95,4 +93,6 @@ IF YOU CAN SEE THE CODE WRITTEN/CHANGED BY THE PROGRAMMER, TRUST IT. YOU DO NOT YOU MUST READ FILES BEFORE WRITING OR CHANGING THEM. NEVER ANNOUNCE WHAT YOU ARE DOING, JUST DO IT! + +{implementation_guidance_section} """ diff --git a/ra_aid/prompts/planning_prompts.py b/ra_aid/prompts/planning_prompts.py index c090e27..1fe9ddf 100644 --- a/ra_aid/prompts/planning_prompts.py +++ b/ra_aid/prompts/planning_prompts.py @@ -48,8 +48,6 @@ Work done so far: {work_log} -{expert_guidance_section} - Guidelines: If you need additional input or assistance from the expert (if expert is available), especially for debugging, deeper logic analysis, or correctness checks, use emit_expert_context to provide all relevant context and wait for the expert's response. @@ -102,4 +100,6 @@ DO NOT USE run_shell_command TO WRITE ANY FILE CONTENTS! USE request_task_implem WORK AND TEST INCREMENTALLY, AND RUN MULTIPLE IMPLEMENTATION TASKS WHERE APPROPRIATE. NEVER ANNOUNCE WHAT YOU ARE DOING, JUST DO IT! + +{expert_guidance_section} """ diff --git a/ra_aid/prompts/reasoning_assist_prompt.py b/ra_aid/prompts/reasoning_assist_prompt.py index 77a4bea..1f7dfd8 100644 --- a/ra_aid/prompts/reasoning_assist_prompt.py +++ b/ra_aid/prompts/reasoning_assist_prompt.py @@ -156,6 +156,10 @@ IF INFORMATION IS TOO COMPLEX TO UNDERSTAND, THE AGENT SHOULD USE ask_expert. Given the available information, tools, and base task or query, write a couple paragraphs about how an agentic system might use the available tools to research the codebase, identify important components, gather key information, and emit key facts and snippets. The focus is on thorough investigation and understanding before any implementation. Remember, the research agent generally should emit research notes at the end of its execution, right before it calls request_implementation if a change or new work is required. +**IF APPLICABLE*, instruct the agent to grep or read actual library code including system include files, python library files, files in node_modules, etc. and emit key snippets on those. The agent is dumb and will need specific paths to directories/files to look in and how to use tools to do this. + +The agent is so dumb it needs you to explicitly say how to use the parameters to the tools as well, e.g. base_dir for ripgrep tool. + Answer quickly and confidently with five sentences at most. DO NOT WRITE CODE diff --git a/ra_aid/prompts/research_prompts.py b/ra_aid/prompts/research_prompts.py index b458b4c..9dce691 100644 --- a/ra_aid/prompts/research_prompts.py +++ b/ra_aid/prompts/research_prompts.py @@ -179,8 +179,6 @@ If this is a top-level README.md or docs folder, start there. If the user explicitly requests implementation, that means you should first perform all the background research for that task, then call request_implementation where the implementation will be carried out. -{expert_guidance_section} - {base_task} @@ -194,6 +192,8 @@ NEVER ANNOUNCE WHAT YOU ARE DOING, JUST DO IT! AS THE RESEARCH AGENT, YOU MUST NOT WRITE OR MODIFY ANY FILES. IF FILE MODIFICATION OR IMPLEMENTATION IS REQUIRED, CALL request_implementation. IF THE USER ASKED YOU TO UPDATE A FILE, JUST DO RESEARCH FIRST, EMIT YOUR RESEARCH NOTES, THEN CALL request_implementation. CALL request_implementation ONLY ONCE! ONCE THE PLAN COMPLETES, YOU'RE DONE. + +{expert_guidance_section} """ ) @@ -206,8 +206,6 @@ You have been spawned by a higher level research agent, so only spawn more resea When you emit research notes, keep it extremely concise and relevant only to the specific research subquery you've been assigned. -{expert_guidance_section} - {base_task} @@ -217,5 +215,7 @@ USER QUERY *ALWAYS* TAKES PRECEDENCE OVER EVERYTHING IN PREVIOUS RESEARCH. KEEP IT SIMPLE NEVER ANNOUNCE WHAT YOU ARE DOING, JUST DO IT! + +{expert_guidance_section} """ ) From b5e4c6404262ca1f56a1ed8d0d56ea991f4f4c9d Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 16:41:09 -0400 Subject: [PATCH 09/14] improve prompts; make list_directory more resilient --- ra_aid/agents/research_agent.py | 4 ++-- ra_aid/prompts/reasoning_assist_prompt.py | 11 ++++++++--- ra_aid/tools/list_directory.py | 2 +- tests/ra_aid/tools/test_list_directory.py | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ra_aid/agents/research_agent.py b/ra_aid/agents/research_agent.py index 667f07b..381cb9e 100644 --- a/ra_aid/agents/research_agent.py +++ b/ra_aid/agents/research_agent.py @@ -110,7 +110,7 @@ def run_research_agent( try: human_input_repository = get_human_input_repository() recent_inputs = human_input_repository.get_recent(1) - if recent_inputs and len(recent_inputs) > 0: + if recent_inputs and len(recent_inputs) > 0 and recent_inputs[0].content != base_task_or_query: last_human_input = recent_inputs[0].content base_task = ( f"{last_human_input}\n{base_task}" @@ -195,7 +195,7 @@ def run_research_agent( tool_info = get_tool_info(tool.func) name = tool.func.__name__ description = inspect.getdoc(tool.func) - tool_metadata.append(f"Tool: {name}\nDescription: {description}\n") + tool_metadata.append(f"Tool: {tool_info}\nDescription: {description}\n") except Exception as e: logger.warning(f"Error getting tool info for {tool}: {e}") diff --git a/ra_aid/prompts/reasoning_assist_prompt.py b/ra_aid/prompts/reasoning_assist_prompt.py index 1f7dfd8..f6c11e7 100644 --- a/ra_aid/prompts/reasoning_assist_prompt.py +++ b/ra_aid/prompts/reasoning_assist_prompt.py @@ -46,6 +46,7 @@ WE DO NOT WANT TO EXCESSIVELY EMIT TINY KEY SNIPPETS --THEY SHOULD BE "paragraph Given the available information, tools, and base task, write a couple paragraphs about how an agentic system might use the available tools to plan the base task, break it down into tasks, and request implementation of those tasks. The agent will not be writing any code at this point, so we should keep it to high level tasks and keep the focus on project planning. The agent has a tendency to do the same work/functin calls over and over again. +The agent is so dumb it needs you to explicitly say how to use the parameters to the tools as well. Answer quickly and confidently with five sentences at most. @@ -61,6 +62,7 @@ PROPOSE THE TASK BREAKDOWN TO THE AGENT. INCLUDE THIS AS A BULLETED LIST IN YOUR WE ARE NOT WRITING ANY CODE AT THIS STAGE. THE AGENT IS VERY FORGETFUL AND YOUR WRITING MUST INCLUDE REMARKS ABOUT HOW IT SHOULD USE *ALL* AVAILABLE TOOLS, INCLUDING AND ESPECIALLY ask_expert. THE AGENT IS DUMB AND NEEDS REALLY DETAILED GUIDANCE LIKE LITERALLY REMINDING IT TO CALL request_task_implementation FOR EACH TASK IN YOUR BULLETED LIST. +YOU MUST MENTION request_task_implementation AT LEAST ONCE. """ REASONING_ASSIST_PROMPT_IMPLEMENTATION = """Current Date: {current_date} @@ -105,6 +107,8 @@ IF ANYTHING AT ALL GOES WRONG, CALL ask_expert. Given the available information, tools, and base task, write a couple paragraphs about how an agentic system might use the available tools to implement the given task definition. The agent will be writing code and making changes at this point. +The agent is so dumb it needs you to explicitly say how to use the parameters to the tools as well. + Answer quickly and confidently with a few sentences at most. WRITE AT LEAST ONE SENTENCE @@ -156,9 +160,7 @@ IF INFORMATION IS TOO COMPLEX TO UNDERSTAND, THE AGENT SHOULD USE ask_expert. Given the available information, tools, and base task or query, write a couple paragraphs about how an agentic system might use the available tools to research the codebase, identify important components, gather key information, and emit key facts and snippets. The focus is on thorough investigation and understanding before any implementation. Remember, the research agent generally should emit research notes at the end of its execution, right before it calls request_implementation if a change or new work is required. -**IF APPLICABLE*, instruct the agent to grep or read actual library code including system include files, python library files, files in node_modules, etc. and emit key snippets on those. The agent is dumb and will need specific paths to directories/files to look in and how to use tools to do this. - -The agent is so dumb it needs you to explicitly say how to use the parameters to the tools as well, e.g. base_dir for ripgrep tool. +The agent is so dumb it needs you to explicitly say how to use the parameters to the tools as well. Answer quickly and confidently with five sentences at most. @@ -170,4 +172,7 @@ REFERENCE ACTUAL TOOL NAMES IN YOUR WRITING, BUT KEEP THE WRITING PLAIN LOGICAL BE DETAILED AND INCLUDE LOGIC BRANCHES FOR WHAT TO DO IF DIFFERENT TOOLS RETURN DIFFERENT THINGS. THINK OF IT AS A FLOW CHART BUT IN NATURAL ENGLISH. THE AGENT IS VERY FORGETFUL AND YOUR WRITING MUST INCLUDE REMARKS ABOUT HOW IT SHOULD USE *ALL* AVAILABLE TOOLS, INCLUDING AND ESPECIALLY ask_expert. + +REMEMBER WE ARE INSTRUCTING THE AGENT **HOW TO DO RESEARCH ABOUT WHAT ALREADY EXISTS** AT THIS POINT USING THE TOOLS AVAILABLE. YOU ARE NOT TO DO THE ACTUAL RESEARCH YOURSELF. IF AN IMPLEMENTATION IS REQUESTED, THE AGENT SHOULD BE INSTRUCTED TO CALL request_task_implementation BUT ONLY AFTER EMITTING RESEARCH NOTES, KEY FACTS, AND KEY SNIPPETS AS RELEVANT. +IT IS IMPERATIVE THAT WE DO NOT START DIRECTLY IMPLEMENTING ANYTHING AT THIS POINT. WE ARE RESEARCHING, THEN CALLING request_implementation *AT MOST ONCE*. """ diff --git a/ra_aid/tools/list_directory.py b/ra_aid/tools/list_directory.py index ad17ced..285f7ff 100644 --- a/ra_aid/tools/list_directory.py +++ b/ra_aid/tools/list_directory.py @@ -200,7 +200,7 @@ def list_directory_tree( """ root_path = Path(path).resolve() if not root_path.exists(): - raise ValueError(f"Path does not exist: {path}") + return f"Error: Path does not exist: {path}" # Load .gitignore patterns if present (only needed for directories) spec = None diff --git a/tests/ra_aid/tools/test_list_directory.py b/tests/ra_aid/tools/test_list_directory.py index ce11ee9..968f620 100644 --- a/tests/ra_aid/tools/test_list_directory.py +++ b/tests/ra_aid/tools/test_list_directory.py @@ -128,7 +128,7 @@ def test_gitignore_patterns(): def test_invalid_path(): """Test error handling for invalid paths""" - with pytest.raises(ValueError, match="Path does not exist"): - list_directory_tree.invoke({"path": "/nonexistent/path"}) + result = list_directory_tree.invoke({"path": "/nonexistent/path"}) + assert "Error: Path does not exist: /nonexistent/path" in result # We now allow files to be passed to list_directory_tree, so we don't test for this case anymore From 3db7cc2ca92aea7cf7037aaa4cd518338b2636ac Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 17:47:31 -0400 Subject: [PATCH 10/14] improve prompt --- ra_aid/prompts/ciayn_prompts.py | 2 ++ ra_aid/prompts/reasoning_assist_prompt.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/ra_aid/prompts/ciayn_prompts.py b/ra_aid/prompts/ciayn_prompts.py index dc9bbd9..f42fccf 100644 --- a/ra_aid/prompts/ciayn_prompts.py +++ b/ra_aid/prompts/ciayn_prompts.py @@ -139,6 +139,8 @@ put_complete_file_contents("/path/to/file.py", '''def example_function(): {last_result_section} + +ANSWER QUICKLY AND CONFIDENTLY WITH A FUNCTION CALL. IF YOU ARE UNSURE, JUST YEET THE BEST FUNCTION CALL YOU CAN. """ # Prompt to send when the model gives no tool call diff --git a/ra_aid/prompts/reasoning_assist_prompt.py b/ra_aid/prompts/reasoning_assist_prompt.py index f6c11e7..dbde1e3 100644 --- a/ra_aid/prompts/reasoning_assist_prompt.py +++ b/ra_aid/prompts/reasoning_assist_prompt.py @@ -63,6 +63,7 @@ WE ARE NOT WRITING ANY CODE AT THIS STAGE. THE AGENT IS VERY FORGETFUL AND YOUR WRITING MUST INCLUDE REMARKS ABOUT HOW IT SHOULD USE *ALL* AVAILABLE TOOLS, INCLUDING AND ESPECIALLY ask_expert. THE AGENT IS DUMB AND NEEDS REALLY DETAILED GUIDANCE LIKE LITERALLY REMINDING IT TO CALL request_task_implementation FOR EACH TASK IN YOUR BULLETED LIST. YOU MUST MENTION request_task_implementation AT LEAST ONCE. +BREAK THE WORK DOWN INTO CHUNKS SMALL ENOUGH EVEN A DUMB/SIMPLE AGENT CAN HANDLE EACH TASK. """ REASONING_ASSIST_PROMPT_IMPLEMENTATION = """Current Date: {current_date} @@ -103,6 +104,7 @@ REMEMBER, IT IS *IMPERATIVE* TO RECORD KEY INFO SUCH AS BUILD/TEST COMMANDS, ETC WE DO NOT WANT TO EMIT REDUNDANT KEY FACTS, SNIPPETS, ETC. WE DO NOT WANT TO EXCESSIVELY EMIT TINY KEY SNIPPETS --THEY SHOULD BE "paragraphs" OF CODE TYPICALLY. IF THERE IS COMPLEX LOGIC, COMPILATION ERRORS, DEBUGGING, THE AGENT SHOULD USE ask_expert. +EXISTING FILES MUST BE READ BEFORE THEY ARE WRITTEN OR MODIFIED. IF ANYTHING AT ALL GOES WRONG, CALL ask_expert. Given the available information, tools, and base task, write a couple paragraphs about how an agentic system might use the available tools to implement the given task definition. The agent will be writing code and making changes at this point. @@ -118,6 +120,8 @@ REFERENCE ACTUAL TOOL NAMES IN YOUR WRITING, BUT KEEP THE WRITING PLAIN LOGICAL BE DETAILED AND INCLUDE LOGIC BRANCHES FOR WHAT TO DO IF DIFFERENT TOOLS RETURN DIFFERENT THINGS. THE AGENT IS VERY FORGETFUL AND YOUR WRITING MUST INCLUDE REMARKS ABOUT HOW IT SHOULD USE *ALL* AVAILABLE TOOLS, INCLUDING AND ESPECIALLY ask_expert. THINK OF IT AS A FLOW CHART BUT IN NATURAL ENGLISH. + +IT IS IMPERATIVE THE AGENT IS INSTRUCTED TO EMIT KEY FACTS AND KEY SNIPPETS AS IT WORKS. THESE MUST BE RELEVANT TO THE TASK AT HAND, ESPECIALLY ANY UPCOMING OR FUTURE WORK. """ REASONING_ASSIST_PROMPT_RESEARCH = """Current Date: {current_date} @@ -175,4 +179,5 @@ THE AGENT IS VERY FORGETFUL AND YOUR WRITING MUST INCLUDE REMARKS ABOUT HOW IT S REMEMBER WE ARE INSTRUCTING THE AGENT **HOW TO DO RESEARCH ABOUT WHAT ALREADY EXISTS** AT THIS POINT USING THE TOOLS AVAILABLE. YOU ARE NOT TO DO THE ACTUAL RESEARCH YOURSELF. IF AN IMPLEMENTATION IS REQUESTED, THE AGENT SHOULD BE INSTRUCTED TO CALL request_task_implementation BUT ONLY AFTER EMITTING RESEARCH NOTES, KEY FACTS, AND KEY SNIPPETS AS RELEVANT. IT IS IMPERATIVE THAT WE DO NOT START DIRECTLY IMPLEMENTING ANYTHING AT THIS POINT. WE ARE RESEARCHING, THEN CALLING request_implementation *AT MOST ONCE*. +IT IS IMPERATIVE THE AGENT EMITS KEY FACTS AND THOROUGH RESEARCH NOTES AT THIS POINT. THE RESEARCH NOTES CAN JUST BE THOUGHTS AT THIS POINT IF IT IS A NEW PROJECT. """ From a18998be0d78426570546faa2fb3460eca32bc60 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 18:01:56 -0400 Subject: [PATCH 11/14] add project info to reasoning assist prompts --- ra_aid/agents/implementation_agent.py | 1 + ra_aid/agents/planning_agent.py | 1 + ra_aid/agents/research_agent.py | 5 +++-- ra_aid/prompts/reasoning_assist_prompt.py | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ra_aid/agents/implementation_agent.py b/ra_aid/agents/implementation_agent.py index 352e241..447f565 100644 --- a/ra_aid/agents/implementation_agent.py +++ b/ra_aid/agents/implementation_agent.py @@ -202,6 +202,7 @@ def run_task_implementation_agent( related_files="\n".join(related_files), env_inv=env_inv, tool_metadata=formatted_tool_metadata, + project_info=formatted_project_info, ) # Show the reasoning assist query in a panel diff --git a/ra_aid/agents/planning_agent.py b/ra_aid/agents/planning_agent.py index e6dd8e8..42355b6 100644 --- a/ra_aid/agents/planning_agent.py +++ b/ra_aid/agents/planning_agent.py @@ -197,6 +197,7 @@ def run_planning_agent( related_files=related_files, env_inv=env_inv, tool_metadata=formatted_tool_metadata, + project_info=formatted_project_info, ) # Show the reasoning assist query in a panel diff --git a/ra_aid/agents/research_agent.py b/ra_aid/agents/research_agent.py index 381cb9e..4acde08 100644 --- a/ra_aid/agents/research_agent.py +++ b/ra_aid/agents/research_agent.py @@ -216,6 +216,7 @@ def run_research_agent( related_files=related_files, env_inv=get_env_inv(), tool_metadata=formatted_tool_metadata, + project_info=formatted_project_info, ) # Show the reasoning assist query in a panel @@ -511,8 +512,8 @@ def run_web_research_agent( console.print(Panel(Markdown(console_message), title="🔬 Researching...")) logger.debug("Web research agent completed successfully") - none_or_fallback_handler = init_fallback_handler(agent, tools) - _result = run_agent_with_retry(agent, prompt, none_or_fallback_handler) + none_or_fallback_handler = agent_utils.init_fallback_handler(agent, tools) + _result = agent_utils.run_agent_with_retry(agent, prompt, none_or_fallback_handler) if _result: # Log web research completion log_work_event(f"Completed web research phase for: {query}") diff --git a/ra_aid/prompts/reasoning_assist_prompt.py b/ra_aid/prompts/reasoning_assist_prompt.py index dbde1e3..ab50738 100644 --- a/ra_aid/prompts/reasoning_assist_prompt.py +++ b/ra_aid/prompts/reasoning_assist_prompt.py @@ -23,6 +23,10 @@ Working Directory: {working_directory} {related_files} + +{project_info} + + {env_inv} @@ -85,6 +89,10 @@ Working Directory: {working_directory} {related_files} + +{project_info} + + {env_inv} @@ -147,6 +155,10 @@ Working Directory: {working_directory} {related_files} + +{project_info} + + {env_inv} @@ -166,6 +178,8 @@ Given the available information, tools, and base task or query, write a couple p The agent is so dumb it needs you to explicitly say how to use the parameters to the tools as well. +ONLY FOR NEW PROJECTS: If this is a new project, most of the focus needs to be on asking the expert, reading/research available library files, emitting key snippets/facts, and most importantly research notes to lay out that we have a new project and what we are building. DO NOT INSTRUCT THE AGENT TO LIST PROJECT DIRECTORIES/READ FILES IF WE ALREADY KNOW THERE ARE NO PROJECT FILES. + Answer quickly and confidently with five sentences at most. DO NOT WRITE CODE From 78983ec20b3952f11588861ded897ce7a99a9af4 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 18:59:42 -0400 Subject: [PATCH 12/14] add trajectory table --- ra_aid/__main__.py | 5 + ra_aid/database/models.py | 35 +- .../repositories/trajectory_repository.py | 381 ++++++++++++++++++ ...07_20250310_184046_add_trajectory_model.py | 100 +++++ 4 files changed, 518 insertions(+), 3 deletions(-) create mode 100644 ra_aid/database/repositories/trajectory_repository.py create mode 100644 ra_aid/migrations/007_20250310_184046_add_trajectory_model.py diff --git a/ra_aid/__main__.py b/ra_aid/__main__.py index dfd7bfa..bf42ec8 100644 --- a/ra_aid/__main__.py +++ b/ra_aid/__main__.py @@ -53,6 +53,9 @@ from ra_aid.database.repositories.human_input_repository import ( from ra_aid.database.repositories.research_note_repository import ( ResearchNoteRepositoryManager, get_research_note_repository ) +from ra_aid.database.repositories.trajectory_repository import ( + TrajectoryRepositoryManager, get_trajectory_repository +) from ra_aid.database.repositories.related_files_repository import ( RelatedFilesRepositoryManager ) @@ -528,6 +531,7 @@ def main(): HumanInputRepositoryManager(db) as human_input_repo, \ ResearchNoteRepositoryManager(db) as research_note_repo, \ RelatedFilesRepositoryManager() as related_files_repo, \ + TrajectoryRepositoryManager(db) as trajectory_repo, \ WorkLogRepositoryManager() as work_log_repo, \ ConfigRepositoryManager(config) as config_repo, \ EnvInvManager(env_data) as env_inv: @@ -537,6 +541,7 @@ def main(): logger.debug("Initialized HumanInputRepository") logger.debug("Initialized ResearchNoteRepository") logger.debug("Initialized RelatedFilesRepository") + logger.debug("Initialized TrajectoryRepository") logger.debug("Initialized WorkLogRepository") logger.debug("Initialized ConfigRepository") logger.debug("Initialized Environment Inventory") diff --git a/ra_aid/database/models.py b/ra_aid/database/models.py index f40d0e1..9cfe0a5 100644 --- a/ra_aid/database/models.py +++ b/ra_aid/database/models.py @@ -42,8 +42,8 @@ def initialize_database(): # to avoid circular imports # Note: This import needs to be here, not at the top level try: - from ra_aid.database.models import KeyFact, KeySnippet, HumanInput, ResearchNote - db.create_tables([KeyFact, KeySnippet, HumanInput, ResearchNote], safe=True) + from ra_aid.database.models import KeyFact, KeySnippet, HumanInput, ResearchNote, Trajectory + db.create_tables([KeyFact, KeySnippet, HumanInput, ResearchNote, Trajectory], safe=True) logger.debug("Ensured database tables exist") except Exception as e: logger.error(f"Error creating tables: {str(e)}") @@ -162,4 +162,33 @@ class ResearchNote(BaseModel): # created_at and updated_at are inherited from BaseModel class Meta: - table_name = "research_note" \ No newline at end of file + table_name = "research_note" + + +class Trajectory(BaseModel): + """ + Model representing an agent trajectory stored in the database. + + Trajectories track the sequence of actions taken by agents, including + tool executions and their results. This enables analysis of agent behavior, + debugging of issues, and reconstruction of the decision-making process. + + Each trajectory record captures details about a single tool execution: + - Which tool was used + - What parameters were passed to the tool + - What result was returned by the tool + - UI rendering data for displaying the tool execution + - Cost and token usage metrics (placeholders for future implementation) + """ + human_input = peewee.ForeignKeyField(HumanInput, backref='trajectories', null=True) + tool_name = peewee.TextField() + tool_parameters = peewee.TextField() # JSON-encoded parameters + tool_result = peewee.TextField() # JSON-encoded result + step_data = peewee.TextField() # JSON-encoded UI rendering data + record_type = peewee.TextField() # Type of trajectory record + cost = peewee.FloatField(null=True) # Placeholder for cost tracking + tokens = peewee.IntegerField(null=True) # Placeholder for token usage tracking + # created_at and updated_at are inherited from BaseModel + + class Meta: + table_name = "trajectory" \ No newline at end of file diff --git a/ra_aid/database/repositories/trajectory_repository.py b/ra_aid/database/repositories/trajectory_repository.py new file mode 100644 index 0000000..14498c0 --- /dev/null +++ b/ra_aid/database/repositories/trajectory_repository.py @@ -0,0 +1,381 @@ +""" +Trajectory repository implementation for database access. + +This module provides a repository implementation for the Trajectory model, +following the repository pattern for data access abstraction. It handles +operations for storing and retrieving agent action trajectories. +""" + +from typing import Dict, List, Optional, Any, Union +import contextvars +import json +import logging + +import peewee + +from ra_aid.database.models import Trajectory, HumanInput +from ra_aid.logging_config import get_logger + +logger = get_logger(__name__) + +# Create contextvar to hold the TrajectoryRepository instance +trajectory_repo_var = contextvars.ContextVar("trajectory_repo", default=None) + + +class TrajectoryRepositoryManager: + """ + Context manager for TrajectoryRepository. + + This class provides a context manager interface for TrajectoryRepository, + using the contextvars approach for thread safety. + + Example: + with DatabaseManager() as db: + with TrajectoryRepositoryManager(db) as repo: + # Use the repository + trajectory = repo.create( + tool_name="ripgrep_search", + tool_parameters={"pattern": "example"} + ) + all_trajectories = repo.get_all() + """ + + def __init__(self, db): + """ + Initialize the TrajectoryRepositoryManager. + + Args: + db: Database connection to use (required) + """ + self.db = db + + def __enter__(self) -> 'TrajectoryRepository': + """ + Initialize the TrajectoryRepository and return it. + + Returns: + TrajectoryRepository: The initialized repository + """ + repo = TrajectoryRepository(self.db) + trajectory_repo_var.set(repo) + return repo + + def __exit__( + self, + exc_type: Optional[type], + exc_val: Optional[Exception], + exc_tb: Optional[object], + ) -> None: + """ + Reset the repository when exiting the context. + + Args: + exc_type: The exception type if an exception was raised + exc_val: The exception value if an exception was raised + exc_tb: The traceback if an exception was raised + """ + # Reset the contextvar to None + trajectory_repo_var.set(None) + + # Don't suppress exceptions + return False + + +def get_trajectory_repository() -> 'TrajectoryRepository': + """ + Get the current TrajectoryRepository instance. + + Returns: + TrajectoryRepository: The current repository instance + + Raises: + RuntimeError: If no repository has been initialized with TrajectoryRepositoryManager + """ + repo = trajectory_repo_var.get() + if repo is None: + raise RuntimeError( + "No TrajectoryRepository available. " + "Make sure to initialize one with TrajectoryRepositoryManager first." + ) + return repo + + +class TrajectoryRepository: + """ + Repository for managing Trajectory database operations. + + This class provides methods for performing CRUD operations on the Trajectory model, + abstracting the database access details from the business logic. It handles + serialization and deserialization of JSON fields for tool parameters, results, + and UI rendering data. + + Example: + with DatabaseManager() as db: + with TrajectoryRepositoryManager(db) as repo: + trajectory = repo.create( + tool_name="ripgrep_search", + tool_parameters={"pattern": "example"} + ) + all_trajectories = repo.get_all() + """ + + def __init__(self, db): + """ + Initialize the repository with a database connection. + + Args: + db: Database connection to use (required) + """ + if db is None: + raise ValueError("Database connection is required for TrajectoryRepository") + self.db = db + + def create( + self, + tool_name: str, + tool_parameters: Dict[str, Any], + tool_result: Optional[Dict[str, Any]] = None, + step_data: Optional[Dict[str, Any]] = None, + record_type: str = "tool_execution", + human_input_id: Optional[int] = None, + cost: Optional[float] = None, + tokens: Optional[int] = None + ) -> Trajectory: + """ + Create a new trajectory record in the database. + + Args: + tool_name: Name of the tool that was executed + tool_parameters: Parameters passed to the tool (will be JSON encoded) + tool_result: Result returned by the tool (will be JSON encoded) + step_data: UI rendering data (will be JSON encoded) + record_type: Type of trajectory record + human_input_id: Optional ID of the associated human input + cost: Optional cost of the operation (placeholder) + tokens: Optional token usage (placeholder) + + Returns: + Trajectory: The newly created trajectory instance + + Raises: + peewee.DatabaseError: If there's an error creating the record + """ + try: + # Serialize JSON fields + tool_parameters_json = json.dumps(tool_parameters) + tool_result_json = json.dumps(tool_result) if tool_result is not None else None + step_data_json = json.dumps(step_data) if step_data is not None else None + + # Create human input reference if provided + human_input = None + if human_input_id is not None: + try: + human_input = HumanInput.get_by_id(human_input_id) + except peewee.DoesNotExist: + logger.warning(f"Human input with ID {human_input_id} not found") + + # Create the trajectory record + trajectory = Trajectory.create( + human_input=human_input, + tool_name=tool_name, + tool_parameters=tool_parameters_json, + tool_result=tool_result_json, + step_data=step_data_json, + record_type=record_type, + cost=cost, + tokens=tokens + ) + logger.debug(f"Created trajectory record ID {trajectory.id} for tool: {tool_name}") + return trajectory + except peewee.DatabaseError as e: + logger.error(f"Failed to create trajectory record: {str(e)}") + raise + + def get(self, trajectory_id: int) -> Optional[Trajectory]: + """ + Retrieve a trajectory record by its ID. + + Args: + trajectory_id: The ID of the trajectory record to retrieve + + Returns: + Optional[Trajectory]: The trajectory instance if found, None otherwise + + Raises: + peewee.DatabaseError: If there's an error accessing the database + """ + try: + return Trajectory.get_or_none(Trajectory.id == trajectory_id) + except peewee.DatabaseError as e: + logger.error(f"Failed to fetch trajectory {trajectory_id}: {str(e)}") + raise + + def update( + self, + trajectory_id: int, + tool_result: Optional[Dict[str, Any]] = None, + step_data: Optional[Dict[str, Any]] = None, + cost: Optional[float] = None, + tokens: Optional[int] = None + ) -> Optional[Trajectory]: + """ + Update an existing trajectory record. + + This is typically used to update the result or metrics after tool execution completes. + + Args: + trajectory_id: The ID of the trajectory record to update + tool_result: Updated tool result (will be JSON encoded) + step_data: Updated UI rendering data (will be JSON encoded) + cost: Updated cost information + tokens: Updated token usage information + + Returns: + Optional[Trajectory]: The updated trajectory if found, None otherwise + + Raises: + peewee.DatabaseError: If there's an error updating the record + """ + try: + # First check if the trajectory exists + trajectory = self.get(trajectory_id) + if not trajectory: + logger.warning(f"Attempted to update non-existent trajectory {trajectory_id}") + return None + + # Update the fields if provided + update_data = {} + + if tool_result is not None: + update_data["tool_result"] = json.dumps(tool_result) + + if step_data is not None: + update_data["step_data"] = json.dumps(step_data) + + if cost is not None: + update_data["cost"] = cost + + if tokens is not None: + update_data["tokens"] = tokens + + if update_data: + query = Trajectory.update(**update_data).where(Trajectory.id == trajectory_id) + query.execute() + logger.debug(f"Updated trajectory record ID {trajectory_id}") + return self.get(trajectory_id) + + return trajectory + except peewee.DatabaseError as e: + logger.error(f"Failed to update trajectory {trajectory_id}: {str(e)}") + raise + + def delete(self, trajectory_id: int) -> bool: + """ + Delete a trajectory record by its ID. + + Args: + trajectory_id: The ID of the trajectory record to delete + + Returns: + bool: True if the record was deleted, False if it wasn't found + + Raises: + peewee.DatabaseError: If there's an error deleting the record + """ + try: + # First check if the trajectory exists + trajectory = self.get(trajectory_id) + if not trajectory: + logger.warning(f"Attempted to delete non-existent trajectory {trajectory_id}") + return False + + # Delete the trajectory + trajectory.delete_instance() + logger.debug(f"Deleted trajectory record ID {trajectory_id}") + return True + except peewee.DatabaseError as e: + logger.error(f"Failed to delete trajectory {trajectory_id}: {str(e)}") + raise + + def get_all(self) -> Dict[int, Trajectory]: + """ + Retrieve all trajectory records from the database. + + Returns: + Dict[int, Trajectory]: Dictionary mapping trajectory IDs to trajectory instances + + Raises: + peewee.DatabaseError: If there's an error accessing the database + """ + try: + return {trajectory.id: trajectory for trajectory in Trajectory.select().order_by(Trajectory.id)} + except peewee.DatabaseError as e: + logger.error(f"Failed to fetch all trajectories: {str(e)}") + raise + + def get_trajectories_by_human_input(self, human_input_id: int) -> List[Trajectory]: + """ + Retrieve all trajectory records associated with a specific human input. + + Args: + human_input_id: The ID of the human input to get trajectories for + + Returns: + List[Trajectory]: List of trajectory instances associated with the human input + + Raises: + peewee.DatabaseError: If there's an error accessing the database + """ + try: + return list(Trajectory.select().where(Trajectory.human_input == human_input_id).order_by(Trajectory.id)) + except peewee.DatabaseError as e: + logger.error(f"Failed to fetch trajectories for human input {human_input_id}: {str(e)}") + raise + + def parse_json_field(self, json_str: Optional[str]) -> Optional[Dict[str, Any]]: + """ + Parse a JSON string into a Python dictionary. + + Args: + json_str: JSON string to parse + + Returns: + Optional[Dict[str, Any]]: Parsed dictionary or None if input is None or invalid + """ + if not json_str: + return None + + try: + return json.loads(json_str) + except json.JSONDecodeError as e: + logger.error(f"Error parsing JSON field: {str(e)}") + return None + + def get_parsed_trajectory(self, trajectory_id: int) -> Optional[Dict[str, Any]]: + """ + Get a trajectory record with JSON fields parsed into dictionaries. + + Args: + trajectory_id: ID of the trajectory to retrieve + + Returns: + Optional[Dict[str, Any]]: Dictionary with trajectory data and parsed JSON fields, + or None if not found + """ + trajectory = self.get(trajectory_id) + if trajectory is None: + return None + + return { + "id": trajectory.id, + "created_at": trajectory.created_at, + "updated_at": trajectory.updated_at, + "tool_name": trajectory.tool_name, + "tool_parameters": self.parse_json_field(trajectory.tool_parameters), + "tool_result": self.parse_json_field(trajectory.tool_result), + "step_data": self.parse_json_field(trajectory.step_data), + "record_type": trajectory.record_type, + "cost": trajectory.cost, + "tokens": trajectory.tokens, + "human_input_id": trajectory.human_input.id if trajectory.human_input else None, + } \ No newline at end of file diff --git a/ra_aid/migrations/007_20250310_184046_add_trajectory_model.py b/ra_aid/migrations/007_20250310_184046_add_trajectory_model.py new file mode 100644 index 0000000..b62ecc9 --- /dev/null +++ b/ra_aid/migrations/007_20250310_184046_add_trajectory_model.py @@ -0,0 +1,100 @@ +"""Peewee migrations -- 007_20250310_184046_add_trajectory_model.py. + +Some examples (model - class or model name):: + + > Model = migrator.orm['table_name'] # Return model in current state by name + > Model = migrator.ModelClass # Return model in current state by name + + > migrator.sql(sql) # Run custom SQL + > migrator.run(func, *args, **kwargs) # Run python function with the given args + > migrator.create_model(Model) # Create a model (could be used as decorator) + > migrator.remove_model(model, cascade=True) # Remove a model + > migrator.add_fields(model, **fields) # Add fields to a model + > migrator.change_fields(model, **fields) # Change fields + > migrator.remove_fields(model, *field_names, cascade=True) + > migrator.rename_field(model, old_field_name, new_field_name) + > migrator.rename_table(model, new_table_name) + > migrator.add_index(model, *col_names, unique=False) + > migrator.add_not_null(model, *field_names) + > migrator.add_default(model, field_name, default) + > migrator.add_constraint(model, name, sql) + > migrator.drop_index(model, *col_names) + > migrator.drop_not_null(model, *field_names) + > migrator.drop_constraints(model, *constraints) + +""" + +from contextlib import suppress + +import peewee as pw +from peewee_migrate import Migrator + + +with suppress(ImportError): + import playhouse.postgres_ext as pw_pext + + +def migrate(migrator: Migrator, database: pw.Database, *, fake=False): + """Create the trajectory table for storing agent action trajectories.""" + + # Check if the table already exists + try: + database.execute_sql("SELECT id FROM trajectory LIMIT 1") + # If we reach here, the table exists + return + except pw.OperationalError: + # Table doesn't exist, safe to create + pass + + @migrator.create_model + class Trajectory(pw.Model): + id = pw.AutoField() + created_at = pw.DateTimeField() + updated_at = pw.DateTimeField() + tool_name = pw.TextField() + tool_parameters = pw.TextField() # JSON-encoded parameters + tool_result = pw.TextField() # JSON-encoded result + step_data = pw.TextField() # JSON-encoded UI rendering data + record_type = pw.TextField() # Type of trajectory record + cost = pw.FloatField(null=True) # Placeholder for cost tracking + tokens = pw.IntegerField(null=True) # Placeholder for token usage tracking + # We'll add the human_input foreign key in a separate step for safety + + class Meta: + table_name = "trajectory" + + # Check if HumanInput model exists before adding the foreign key + try: + HumanInput = migrator.orm['human_input'] + + # Only add the foreign key if the human_input_id column doesn't already exist + try: + database.execute_sql("SELECT human_input_id FROM trajectory LIMIT 1") + except pw.OperationalError: + # Column doesn't exist, safe to add + migrator.add_fields( + 'trajectory', + human_input=pw.ForeignKeyField( + HumanInput, + null=True, + field='id', + on_delete='SET NULL' + ) + ) + except KeyError: + # HumanInput doesn't exist, we'll skip adding the foreign key + pass + + +def rollback(migrator: Migrator, database: pw.Database, *, fake=False): + """Remove the trajectory table.""" + + # First remove any foreign key fields + try: + migrator.remove_fields('trajectory', 'human_input') + except pw.OperationalError: + # Field might not exist, that's fine + pass + + # Then remove the model + migrator.remove_model('trajectory') \ No newline at end of file From b3010bb649806e7d009bef2cae2cab01df724eec Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 19:12:14 -0400 Subject: [PATCH 13/14] add error info to trajectory records --- ra_aid/database/models.py | 5 +++ .../repositories/trajectory_repository.py | 42 +++++++++++++++++-- ...07_20250310_184046_add_trajectory_model.py | 4 ++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/ra_aid/database/models.py b/ra_aid/database/models.py index 9cfe0a5..f83acc5 100644 --- a/ra_aid/database/models.py +++ b/ra_aid/database/models.py @@ -179,6 +179,7 @@ class Trajectory(BaseModel): - What result was returned by the tool - UI rendering data for displaying the tool execution - Cost and token usage metrics (placeholders for future implementation) + - Error information (when a tool execution fails) """ human_input = peewee.ForeignKeyField(HumanInput, backref='trajectories', null=True) tool_name = peewee.TextField() @@ -188,6 +189,10 @@ class Trajectory(BaseModel): record_type = peewee.TextField() # Type of trajectory record cost = peewee.FloatField(null=True) # Placeholder for cost tracking tokens = peewee.IntegerField(null=True) # Placeholder for token usage tracking + is_error = peewee.BooleanField(default=False) # Flag indicating if this record represents an error + error_message = peewee.TextField(null=True) # The error message + error_type = peewee.TextField(null=True) # The type/class of the error + error_details = peewee.TextField(null=True) # Additional error details like stack traces or context # created_at and updated_at are inherited from BaseModel class Meta: diff --git a/ra_aid/database/repositories/trajectory_repository.py b/ra_aid/database/repositories/trajectory_repository.py index 14498c0..4b0f8ec 100644 --- a/ra_aid/database/repositories/trajectory_repository.py +++ b/ra_aid/database/repositories/trajectory_repository.py @@ -139,7 +139,11 @@ class TrajectoryRepository: record_type: str = "tool_execution", human_input_id: Optional[int] = None, cost: Optional[float] = None, - tokens: Optional[int] = None + tokens: Optional[int] = None, + is_error: bool = False, + error_message: Optional[str] = None, + error_type: Optional[str] = None, + error_details: Optional[str] = None ) -> Trajectory: """ Create a new trajectory record in the database. @@ -153,6 +157,10 @@ class TrajectoryRepository: human_input_id: Optional ID of the associated human input cost: Optional cost of the operation (placeholder) tokens: Optional token usage (placeholder) + is_error: Flag indicating if this record represents an error (default: False) + error_message: The error message (if is_error is True) + error_type: The type/class of the error (if is_error is True) + error_details: Additional error details like stack traces (if is_error is True) Returns: Trajectory: The newly created trajectory instance @@ -183,7 +191,11 @@ class TrajectoryRepository: step_data=step_data_json, record_type=record_type, cost=cost, - tokens=tokens + tokens=tokens, + is_error=is_error, + error_message=error_message, + error_type=error_type, + error_details=error_details ) logger.debug(f"Created trajectory record ID {trajectory.id} for tool: {tool_name}") return trajectory @@ -216,7 +228,11 @@ class TrajectoryRepository: tool_result: Optional[Dict[str, Any]] = None, step_data: Optional[Dict[str, Any]] = None, cost: Optional[float] = None, - tokens: Optional[int] = None + tokens: Optional[int] = None, + is_error: Optional[bool] = None, + error_message: Optional[str] = None, + error_type: Optional[str] = None, + error_details: Optional[str] = None ) -> Optional[Trajectory]: """ Update an existing trajectory record. @@ -229,6 +245,10 @@ class TrajectoryRepository: step_data: Updated UI rendering data (will be JSON encoded) cost: Updated cost information tokens: Updated token usage information + is_error: Flag indicating if this record represents an error + error_message: The error message + error_type: The type/class of the error + error_details: Additional error details like stack traces Returns: Optional[Trajectory]: The updated trajectory if found, None otherwise @@ -257,6 +277,18 @@ class TrajectoryRepository: if tokens is not None: update_data["tokens"] = tokens + + if is_error is not None: + update_data["is_error"] = is_error + + if error_message is not None: + update_data["error_message"] = error_message + + if error_type is not None: + update_data["error_type"] = error_type + + if error_details is not None: + update_data["error_details"] = error_details if update_data: query = Trajectory.update(**update_data).where(Trajectory.id == trajectory_id) @@ -378,4 +410,8 @@ class TrajectoryRepository: "cost": trajectory.cost, "tokens": trajectory.tokens, "human_input_id": trajectory.human_input.id if trajectory.human_input else None, + "is_error": trajectory.is_error, + "error_message": trajectory.error_message, + "error_type": trajectory.error_type, + "error_details": trajectory.error_details, } \ No newline at end of file diff --git a/ra_aid/migrations/007_20250310_184046_add_trajectory_model.py b/ra_aid/migrations/007_20250310_184046_add_trajectory_model.py index b62ecc9..3eb20cd 100644 --- a/ra_aid/migrations/007_20250310_184046_add_trajectory_model.py +++ b/ra_aid/migrations/007_20250310_184046_add_trajectory_model.py @@ -58,6 +58,10 @@ def migrate(migrator: Migrator, database: pw.Database, *, fake=False): record_type = pw.TextField() # Type of trajectory record cost = pw.FloatField(null=True) # Placeholder for cost tracking tokens = pw.IntegerField(null=True) # Placeholder for token usage tracking + is_error = pw.BooleanField(default=False) # Flag indicating if this record represents an error + error_message = pw.TextField(null=True) # The error message + error_type = pw.TextField(null=True) # The type/class of the error + error_details = pw.TextField(null=True) # Additional error details like stack traces or context # We'll add the human_input foreign key in a separate step for safety class Meta: From 909825bf1b02b7027beb715f2a6a306a03a57ca3 Mon Sep 17 00:00:00 2001 From: AI Christianson Date: Mon, 10 Mar 2025 19:37:39 -0400 Subject: [PATCH 14/14] refactor: extract get_most_recent_id --- ra_aid/agents/key_facts_gc_agent.py | 8 ++------ ra_aid/agents/key_snippets_gc_agent.py | 8 ++------ ra_aid/agents/research_agent.py | 14 ++++++++------ ra_aid/agents/research_notes_gc_agent.py | 8 ++------ .../repositories/human_input_repository.py | 19 +++++++++++++++++++ ra_aid/tools/memory.py | 12 +++--------- 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/ra_aid/agents/key_facts_gc_agent.py b/ra_aid/agents/key_facts_gc_agent.py index d158c64..e8fbd38 100644 --- a/ra_aid/agents/key_facts_gc_agent.py +++ b/ra_aid/agents/key_facts_gc_agent.py @@ -48,9 +48,7 @@ def delete_key_facts(fact_ids: List[int]) -> str: # Try to get the current human input to protect its facts current_human_input_id = None try: - recent_inputs = get_human_input_repository().get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - current_human_input_id = recent_inputs[0].id + current_human_input_id = get_human_input_repository().get_most_recent_id() except Exception as e: console.print(f"Warning: Could not retrieve current human input: {str(e)}") @@ -133,9 +131,7 @@ def run_key_facts_gc_agent() -> None: # Try to get the current human input ID to exclude its facts current_human_input_id = None try: - recent_inputs = get_human_input_repository().get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - current_human_input_id = recent_inputs[0].id + current_human_input_id = get_human_input_repository().get_most_recent_id() except Exception as e: console.print(f"Warning: Could not retrieve current human input: {str(e)}") diff --git a/ra_aid/agents/key_snippets_gc_agent.py b/ra_aid/agents/key_snippets_gc_agent.py index b9d55ef..6fb86fa 100644 --- a/ra_aid/agents/key_snippets_gc_agent.py +++ b/ra_aid/agents/key_snippets_gc_agent.py @@ -46,9 +46,7 @@ def delete_key_snippets(snippet_ids: List[int]) -> str: # Try to get the current human input to protect its snippets current_human_input_id = None try: - recent_inputs = get_human_input_repository().get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - current_human_input_id = recent_inputs[0].id + current_human_input_id = get_human_input_repository().get_most_recent_id() except Exception as e: console.print(f"Warning: Could not retrieve current human input: {str(e)}") @@ -125,9 +123,7 @@ def run_key_snippets_gc_agent() -> None: # Try to get the current human input ID to exclude its snippets current_human_input_id = None try: - recent_inputs = get_human_input_repository().get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - current_human_input_id = recent_inputs[0].id + current_human_input_id = get_human_input_repository().get_most_recent_id() except Exception as e: console.print(f"Warning: Could not retrieve current human input: {str(e)}") diff --git a/ra_aid/agents/research_agent.py b/ra_aid/agents/research_agent.py index 4acde08..966321f 100644 --- a/ra_aid/agents/research_agent.py +++ b/ra_aid/agents/research_agent.py @@ -109,12 +109,14 @@ def run_research_agent( base_task = base_task_or_query try: human_input_repository = get_human_input_repository() - recent_inputs = human_input_repository.get_recent(1) - if recent_inputs and len(recent_inputs) > 0 and recent_inputs[0].content != base_task_or_query: - last_human_input = recent_inputs[0].content - base_task = ( - f"{last_human_input}\n{base_task}" - ) + most_recent_id = human_input_repository.get_most_recent_id() + if most_recent_id is not None: + recent_input = human_input_repository.get(most_recent_id) + if recent_input and recent_input.content != base_task_or_query: + last_human_input = recent_input.content + base_task = ( + f"{last_human_input}\n{base_task}" + ) except RuntimeError as e: logger.error(f"Failed to access human input repository: {str(e)}") # Continue without appending last human input diff --git a/ra_aid/agents/research_notes_gc_agent.py b/ra_aid/agents/research_notes_gc_agent.py index 70bb91f..fbc0d60 100644 --- a/ra_aid/agents/research_notes_gc_agent.py +++ b/ra_aid/agents/research_notes_gc_agent.py @@ -48,9 +48,7 @@ def delete_research_notes(note_ids: List[int]) -> str: # Try to get the current human input to protect its notes current_human_input_id = None try: - recent_inputs = get_human_input_repository().get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - current_human_input_id = recent_inputs[0].id + current_human_input_id = get_human_input_repository().get_most_recent_id() except Exception as e: console.print(f"Warning: Could not retrieve current human input: {str(e)}") @@ -138,9 +136,7 @@ def run_research_notes_gc_agent(threshold: int = 30) -> None: # Try to get the current human input ID to exclude its notes current_human_input_id = None try: - recent_inputs = get_human_input_repository().get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - current_human_input_id = recent_inputs[0].id + current_human_input_id = get_human_input_repository().get_most_recent_id() except Exception as e: console.print(f"Warning: Could not retrieve current human input: {str(e)}") diff --git a/ra_aid/database/repositories/human_input_repository.py b/ra_aid/database/repositories/human_input_repository.py index f8f89ff..f20853d 100644 --- a/ra_aid/database/repositories/human_input_repository.py +++ b/ra_aid/database/repositories/human_input_repository.py @@ -257,6 +257,25 @@ class HumanInputRepository: except peewee.DatabaseError as e: logger.error(f"Failed to fetch recent human inputs: {str(e)}") raise + + def get_most_recent_id(self) -> Optional[int]: + """ + Get the ID of the most recent human input record. + + Returns: + Optional[int]: The ID of the most recent human input, or None if no records exist + + Raises: + peewee.DatabaseError: If there's an error accessing the database + """ + try: + recent_inputs = self.get_recent(1) + if recent_inputs and len(recent_inputs) > 0: + return recent_inputs[0].id + return None + except peewee.DatabaseError as e: + logger.error(f"Failed to fetch most recent human input ID: {str(e)}") + raise def get_by_source(self, source: str) -> List[HumanInput]: """ diff --git a/ra_aid/tools/memory.py b/ra_aid/tools/memory.py index c768638..96b402c 100644 --- a/ra_aid/tools/memory.py +++ b/ra_aid/tools/memory.py @@ -54,9 +54,7 @@ def emit_research_notes(notes: str) -> str: human_input_id = None try: human_input_repo = get_human_input_repository() - recent_inputs = human_input_repo.get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - human_input_id = recent_inputs[0].id + human_input_id = human_input_repo.get_most_recent_id() except RuntimeError as e: logger.warning(f"No HumanInputRepository available: {str(e)}") except Exception as e: @@ -109,9 +107,7 @@ def emit_key_facts(facts: List[str]) -> str: human_input_id = None try: human_input_repo = get_human_input_repository() - recent_inputs = human_input_repo.get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - human_input_id = recent_inputs[0].id + human_input_id = human_input_repo.get_most_recent_id() except RuntimeError as e: logger.warning(f"No HumanInputRepository available: {str(e)}") except Exception as e: @@ -186,9 +182,7 @@ def emit_key_snippet(snippet_info: SnippetInfo) -> str: human_input_id = None try: human_input_repo = get_human_input_repository() - recent_inputs = human_input_repo.get_recent(1) - if recent_inputs and len(recent_inputs) > 0: - human_input_id = recent_inputs[0].id + human_input_id = human_input_repo.get_most_recent_id() except RuntimeError as e: logger.warning(f"No HumanInputRepository available: {str(e)}") except Exception as e: