diff --git a/packages/napthaai/customs/prediction_request_rag_claude/prediction_request_rag_claude.py b/packages/napthaai/customs/prediction_request_rag_claude/prediction_request_rag_claude.py new file mode 100644 index 00000000..a0804bde --- /dev/null +++ b/packages/napthaai/customs/prediction_request_rag_claude/prediction_request_rag_claude.py @@ -0,0 +1,596 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +import re +import json +import faiss +import PyPDF2 +import requests +import anthropic +import numpy as np +from io import BytesIO +from openai import OpenAI +from itertools import islice +from pydantic import BaseModel +from collections import defaultdict +from googleapiclient.discovery import build +from concurrent.futures import Future, ThreadPoolExecutor +from readability import Document as ReadabilityDocument +from requests.exceptions import RequestException, TooManyRedirects +from markdownify import markdownify as md +from typing import Any, Dict, Generator, List, Optional, Tuple, Callable +from tiktoken import encoding_for_model + + +DEFAULT_CLAUDE_SETTINGS = { + "max_tokens": 1000, + "temperature": 0, +} +MAX_TOKENS = { + 'claude-2': 200_0000, + 'claude-2.1': 200_0000, + 'claude-3-haiku-20240307': 200_0000, + 'claude-3-sonnet-20240229': 200_0000, + 'claude-3-opus-20240229': 200_0000, +} +ALLOWED_TOOLS = [ + "prediction-request-rag-claude", +] +ALLOWED_MODELS = [ + "claude-2", + "claude-2.1", + "claude-3-haiku-20240307", + "claude-3-sonnet-20240229", + "claude-3-opus-20240229", +] +TOOL_TO_ENGINE = {tool: "claude-3-haiku-20240307" for tool in ALLOWED_TOOLS} + +DEFAULT_NUM_URLS = defaultdict(lambda: 3) +DEFAULT_NUM_QUERIES = defaultdict(lambda: 3) +NUM_URLS_PER_QUERY = 5 +SPLITTER_CHUNK_SIZE = 1800 +SPLITTER_OVERLAP = 50 +EMBEDDING_MODEL = "text-embedding-ada-002" +EMBEDDING_BATCH_SIZE = 1000 +EMBEDDING_SIZE = 1536 +SPLITTER_MAX_TOKENS = 1800 +SPLITTER_OVERLAP = 50 +NUM_NEIGHBORS = 4 +HTTP_TIMEOUT = 20 +HTTP_MAX_REDIRECTS = 5 +HTTP_MAX_RETIES = 2 + + +PREDICTION_PROMPT = """ +Here is some additional background information that may be relevant to the question: + {ADDITIONAL_INFORMATION} + +A user has asked the following: + + {USER_PROMPT} + +Carefully consider the user's question and the additional information provided. Think through the likelihood of the event the user asked about actually happening in the future, based on the details given. Write out your reasoning and analysis in a section. + +Now, based on your analysis above, provide a prediction of the probability the event will happen, as p_yes between 0 and 1. Also provide the probability it will not happen, as p_no between 0 and 1. The two probabilities should sum to 1. + +p_yes: p_no: + +How useful was the additional information in allowing you to make a prediction? Provide your rating as info_utility, a number between 0 and 1. + +info_utility: + +Finally, considering everything, what is your overall confidence in your prediction? Provide your confidence as a number between 0 and 1. + +confidence: + +Make sure the values you provide are between 0 and 1. And p_yes and p_no should sum to 1. + +Your response should be structured as follows: + + + + + +""" + +URL_QUERY_PROMPT = """ +Here is the user prompt: {USER_PROMPT} + +Please read the prompt carefully and identify the key pieces of information that need to be searched for in order to comprehensively address the topic. + +Brainstorm a list of {NUM_QUERIES} different search queries that cover various aspects of the user prompt. Each query should be focused on a specific sub-topic or question related to the overarching prompt. + +Please write each search query inside its own tags, like this: example search query here + +The queries should be concise while still containing enough information to return relevant search results. Focus the queries on gathering factual information to address the prompt rather than opinions. + +After you have written all {NUM_QUERIES} search queries, please submit your final response. + + +""" + +SYSTEM_PROMPT = """You are a world class algorithm for generating structured output from a given input.""" + +class Document(BaseModel): + text: str + url: str + embedding: Optional[List[float]] = None + + +def count_tokens(text: str, model: str) -> int: + """Count the number of tokens in a text.""" + enc = encoding_for_model(model) + return len(enc.encode(text)) + + +def multi_queries( + client: anthropic.Anthropic, + prompt: str, + engine: str, + num_queries: int, + counter_callback: Optional[Callable[[int, int, str], None]] = None, + temperature: Optional[float] = DEFAULT_CLAUDE_SETTINGS["temperature"], + max_tokens: Optional[int] = DEFAULT_CLAUDE_SETTINGS["max_tokens"], +) -> List[str]: + """Generate multiple queries for fetching information from the web.""" + url_query_prompt = URL_QUERY_PROMPT.format( + USER_PROMPT=prompt, NUM_QUERIES=num_queries + ) + + messages = [ + {"role": "user", "content": url_query_prompt}, + ] + + response = client.messages.create( + model=engine, + messages=messages, + system=SYSTEM_PROMPT, + temperature=temperature, + max_tokens=max_tokens, + ) + if counter_callback: + counter_callback( + input_tokens=response.usage.input_tokens, + output_tokens=response.usage.output_tokens, + model=engine, + token_counter=count_tokens, + ) + queries = parser_query_response(response.content[0].text, num_queries=num_queries) + queries.append(prompt) + + return queries, counter_callback + + +def parser_query_response(response: str, num_queries: int = 5) -> List[str]: + """Parse the response from the query generation model with optional enhancements.""" + queries = response.split("")[1].split("")[0].split("\n") + parsed_queries = [query.strip() for query in queries if query.strip()] + enhanced_queries = [] + + for query in parsed_queries: + if query[0].isdigit(): + query = ". ".join(query.split(". ")[1:]) + query = query.replace('"', '') + enhanced_queries.append(query) + + if len(enhanced_queries) == num_queries * 2: + enhanced_queries = enhanced_queries[::2] + + # Remove doubel quotes from the queries + final_queries = [query.replace('"', '') for query in enhanced_queries] + + # if there are any xml tags in the queries, remove them + final_queries = [re.sub(r'<[^>]*>', '', query) for query in final_queries] + + return final_queries + + +def search_google( + query: str, + api_key: str, + engine: str, + num: int +) -> List[str]: + """Search Google for the given query.""" + service = build("customsearch", "v1", developerKey=api_key) + search = ( + service.cse() + .list( + q=query, + cx=engine, + num=num, + ) + .execute() + ) + return [result["link"] for result in search["items"]] + + +def get_urls_from_queries( + queries: List[str], api_key: str, engine: str, num: int +) -> List[str]: + """Get URLs from search engine queries""" + results = [] + for query in queries: + try: + for url in search_google( + query=query, + api_key=api_key, + engine=engine, + num=num, + ): + results.append(url) + except Exception: + pass + unique_results = list(set(results)) + return unique_results + + +def extract_text( + html: str, + num_words: Optional[int] = None, +) -> str: + """Extract text from a single HTML document""" + text = ReadabilityDocument(html).summary() + + # use html2text to convert HTML to markdown + text = md(text, heading_style="ATX") + + if text is None: + return None + + if num_words: + text = " ".join(text.split()[:num_words]) + else: + text = " ".join(text.split()) + + doc = Document(text=text, url="") + return doc + + +def extract_text_from_pdf(url: str, num_words: Optional[int] = None) -> str: + """Extract text from a PDF document at the given URL.""" + try: + response = requests.get(url, timeout=HTTP_TIMEOUT) + response.raise_for_status() + + if "application/pdf" not in response.headers.get("Content-Type", ""): + return ValueError("URL does not point to a PDF document") + + with BytesIO(response.content) as pdf_file: + reader = PyPDF2.PdfReader(pdf_file) + text = "" + for page in reader.pages: + text += page.extract_text() + + doc = Document(text=text[:num_words] if num_words else text, date="", url=url) + print(f"Using PDF: {url}: {doc.text[:300]}...") + return doc + + except Exception as e: + print(f"An error occurred: {e}") + return None + + +def process_in_batches( + urls: List[str], + window: int = 5, + timeout: int = HTTP_TIMEOUT, + max_redirects: int = HTTP_MAX_REDIRECTS, + retries: int = HTTP_MAX_RETIES, +) -> Generator[None, None, List[Tuple[Optional[Future], str]]]: + """Iter URLs in batches with improved error handling and retry mechanism.""" + with ThreadPoolExecutor() as executor, requests.Session() as session: + session.max_redirects = max_redirects + for i in range(0, len(urls), window): + batch = urls[i : i + window] + futures = [] + for url in batch: + future = None + attempt = 0 + while attempt < retries: + try: + future = executor.submit(session.get, url, timeout=timeout) + break + except (TooManyRedirects, RequestException) as e: + print(f"Attempt {attempt + 1} failed for {url}: {e}") + attempt += 1 + if attempt == retries: + print(f"Max retries reached for {url}. Moving to next URL.") + futures.append((future, url)) + yield futures + + +def extract_texts(urls: List[str], num_words: Optional[int] = None) -> List[Document]: + """Extract texts from URLs with improved error handling, excluding failed URLs.""" + extracted_texts = [] + for batch in process_in_batches(urls=urls): + for future, url in batch: + if future is None: + continue + try: + result = future.result() + if result.status_code == 200: + # Check if URL ends with .pdf or content starts with %PDF + if url.endswith('.pdf') or result.content[:4] == b'%PDF': + doc = extract_text_from_pdf(url, num_words=num_words) + else: + doc = extract_text(html=result.text, num_words=num_words) + doc.url = url + extracted_texts.append(doc) + except Exception as e: + print(f"Error processing {url}: {e}") + continue + return extracted_texts + + +def find_similar_chunks( + client: OpenAI, + query: str, + docs_with_embeddings: List[Document], + k: int = 4 +) -> List: + """Similarity search to find similar chunks to a query""" + query_embedding = ( + client.embeddings.create( + model=EMBEDDING_MODEL, + input=query, + ) + .data[0] + .embedding + ) + + index = faiss.IndexFlatIP(EMBEDDING_SIZE) + index.add(np.array([doc.embedding for doc in docs_with_embeddings])) + D, I = index.search(np.array([query_embedding]), k) + + return [docs_with_embeddings[i] for i in I[0]] + + +def get_embeddings( + client: OpenAI, + split_docs: List[Document] +) -> List[Document]: + """Get embeddings for the split documents.""" + for batch_start in range(0, len(split_docs), EMBEDDING_BATCH_SIZE): + batch_end = batch_start + EMBEDDING_BATCH_SIZE + batch = [doc.text for doc in split_docs[batch_start:batch_end]] + response = client.embeddings.create( + model=EMBEDDING_MODEL, + input=batch, + ) + for i, be in enumerate(response.data): + assert i == be.index + batch_embeddings = [e.embedding for e in response.data] + for i, doc in enumerate(split_docs[batch_start:batch_end]): + doc.embedding = batch_embeddings[i] + return split_docs + + +def recursive_character_text_splitter(text, max_tokens, overlap): + if len(text) <= max_tokens: + return [text] + else: + return [text[i:i+max_tokens] for i in range(0, len(text), max_tokens - overlap)] + + +def fetch_additional_information( + client: anthropic.Anthropic, + client_openai: OpenAI, + prompt: str, + engine: str, + google_api_key: Optional[str], + google_engine_id: Optional[str], + counter_callback: Optional[Callable[[int, int, str], None]] = None, + source_links: Optional[List[str]] = None, + num_urls: Optional[int] = DEFAULT_NUM_URLS, + num_queries: Optional[int] = DEFAULT_NUM_QUERIES, + temperature: Optional[float] = DEFAULT_CLAUDE_SETTINGS["temperature"], + max_tokens: Optional[int] = DEFAULT_CLAUDE_SETTINGS["max_tokens"] +) -> Tuple[str, Callable[[int, int, str], None]]: + + """Fetch additional information to help answer the user prompt.""" + + # generate multiple queries for fetching information from the web + + try: + queries, counter_callback = multi_queries( + client=client, + prompt=prompt, + engine=engine, + num_queries=num_queries, + counter_callback=counter_callback, + temperature=temperature, + max_tokens=max_tokens, + ) + print(f"Queries: {queries}") + except Exception as e: + print(f"Error generating queries: {e}") + queries = [prompt] + + # get the top URLs for the queries + if not source_links: + urls = get_urls_from_queries( + queries=queries, + api_key=google_api_key, + engine=google_engine_id, + num=NUM_URLS_PER_QUERY, + ) + print(f"URLs: {urls}") + + urls = list(set(urls)) + + # Extract text and dates from the URLs + docs = extract_texts( + urls=urls, + ) + else: + docs = [] + for url, content in islice(source_links.items(), num_urls or len(source_links)): + doc = extract_text(html=content) + doc.url = url + docs.append(doc) + + # Remove None values from the list + docs = [doc for doc in docs if doc] + + # remove empty documents "" + filtered_docs = [doc for doc in docs if hasattr(doc, 'text') and doc.text != ""] + + # Chunk the documents + split_docs = [] + for doc in filtered_docs: + try: + t = recursive_character_text_splitter( + doc.text, SPLITTER_CHUNK_SIZE, SPLITTER_OVERLAP + ) + split_docs.extend( + [Document(text=chunk, url=doc.url) for chunk in t] + ) + except Exception as e: + print(f"Error splitting document: {e}") + continue + print(f"Split Docs: {len(split_docs)}") + + # Remove None values from the list + split_docs = [doc for doc in split_docs if doc] + + # Embed the documents + docs_with_embeddings = get_embeddings(client_openai, split_docs) + print(f"Docs with embeddings: {len(docs_with_embeddings)}") + + # Find similar chunks + similar_chunks = find_similar_chunks( + client_openai, + query=prompt, + docs_with_embeddings=docs_with_embeddings, + k=NUM_NEIGHBORS, + ) + print(f"Similar Chunks: {len(similar_chunks)}") + + # Format the additional information + additional_information = "\n".join( + [ + f"ARTICLE {i}, URL: {doc.url}, CONTENT: {doc.text}\n" + for i, doc in enumerate(similar_chunks) + ] + ) + + return additional_information, counter_callback + + +def extract_question(prompt: str) -> str: + pattern = r'\"(.*?)\"' + try: + question = re.findall(pattern, prompt)[0] + except Exception as e: + print(f"Error extracting question: {e}") + question = prompt + + return question + + +def parser_prediction_response(response: str) -> str: + """Parse the response from the prediction model.""" + results = {} + for key in ["p_yes", "p_no", "info_utility", "confidence"]: + try: + value = response.split(f"<{key}>")[1].split(f"")[0].strip() + if key in ["p_yes", "p_no", "info_utility", "confidence"]: + value = float(value) + results[key] = value + except Exception: + raise ValueError(f"Error parsing {key}") + + results = json.dumps(results) + return results + + +def run(**kwargs) -> Tuple[Optional[str], Any, Optional[Dict[str, Any]], Any]: + """Run the task""" + + tool = kwargs["tool"] + model = kwargs.get("model", TOOL_TO_ENGINE[tool]) + prompt = extract_question(kwargs["prompt"]) + max_tokens = kwargs.get("max_tokens", DEFAULT_CLAUDE_SETTINGS["max_tokens"]) + temperature = kwargs.get("temperature", DEFAULT_CLAUDE_SETTINGS["temperature"]) + num_urls = kwargs.get("num_urls", DEFAULT_NUM_URLS[tool]) + num_queries = kwargs.get("num_queries", DEFAULT_NUM_QUERIES[tool]) + counter_callback = kwargs.get("counter_callback", None) + api_keys = kwargs.get("api_keys", {}) + google_api_key = api_keys.get("google_api_key", None) + google_engine_id = api_keys.get("google_engine_id", None) + client = anthropic.Anthropic(api_key=api_keys["anthropic"]) + client_openai = OpenAI(api_key=api_keys["openai"]) + + # Make sure the model is supported + if model not in ALLOWED_MODELS: + raise ValueError(f"Model {model} not supported.") + + # make sure the tool is supported + if tool not in ALLOWED_TOOLS: + raise ValueError(f"Tool {tool} not supported.") + + engine = kwargs.get("model", TOOL_TO_ENGINE[tool]) + print(f"ENGINE: {engine}") + + additional_information, counter_callback = fetch_additional_information( + client=client, + client_openai=client_openai, + prompt=prompt, + engine=engine, + google_api_key=google_api_key, + google_engine_id=google_engine_id, + counter_callback=counter_callback, + source_links=kwargs.get("source_links", None), + num_urls=num_urls, + num_queries=num_queries, + temperature=temperature, + max_tokens=max_tokens, + ) + + # Generate the prediction prompt + prediction_prompt = PREDICTION_PROMPT.format( + ADDITIONAL_INFORMATION=additional_information, + USER_PROMPT=prompt, + ) + + # Generate the prediction + messages = [ + {"role": "user", "content": prediction_prompt}, + ] + + response = client.messages.create( + model=engine, + messages=messages, + system=SYSTEM_PROMPT, + temperature=temperature, + max_tokens=max_tokens, + ) + + if counter_callback: + counter_callback( + input_tokens=response.usage.input_tokens, + output_tokens=response.usage.output_tokens, + model=engine, + token_counter=count_tokens, + ) + + results = parser_prediction_response(response.content[0].text) + + return results, prediction_prompt, None, counter_callback diff --git a/packages/napthaai/customs/prediction_request_reasoning_claude/__init__.py b/packages/napthaai/customs/prediction_request_reasoning_claude/__init__.py new file mode 100644 index 00000000..9f4fbd55 --- /dev/null +++ b/packages/napthaai/customs/prediction_request_reasoning_claude/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the resolve market reasoning tool.""" diff --git a/packages/napthaai/customs/prediction_request_reasoning_claude/prediction_request_reasoning_claude.py b/packages/napthaai/customs/prediction_request_reasoning_claude/prediction_request_reasoning_claude.py new file mode 100644 index 00000000..44f87119 --- /dev/null +++ b/packages/napthaai/customs/prediction_request_reasoning_claude/prediction_request_reasoning_claude.py @@ -0,0 +1,746 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2024 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module implements a Mech tool for binary predictions.""" + +import re +import json +import faiss +import PyPDF2 +import requests +import anthropic +import numpy as np +from io import BytesIO +from itertools import islice +from openai import OpenAI +from pydantic import BaseModel +from collections import defaultdict +from tiktoken import encoding_for_model +from markdownify import markdownify as md +from googleapiclient.discovery import build +from readability import Document as ReadabilityDocument +from concurrent.futures import Future, ThreadPoolExecutor +from requests.exceptions import RequestException, TooManyRedirects +from typing import Any, Dict, Generator, List, Optional, Tuple, Callable + + +DEFAULT_CLAUDE_SETTINGS = { + "max_tokens": 1000, + "temperature": 0, +} +MAX_TOKENS = { + 'claude-2': 200_0000, + 'claude-2.1': 200_0000, + 'claude-3-haiku-20240307': 200_0000, + 'claude-3-sonnet-20240229': 200_0000, + 'claude-3-opus-20240229': 200_0000, +} +ALLOWED_TOOLS = [ + "prediction-request-reasoning-claude", +] +ALLOWED_MODELS = [ + "claude-2", + "claude-2.1", + "claude-3-haiku-20240307", + "claude-3-sonnet-20240229", + "claude-3-opus-20240229", +] +TOOL_TO_ENGINE = {tool: "claude-3-haiku-20240307" for tool in ALLOWED_TOOLS} +DEFAULT_NUM_URLS = defaultdict(lambda: 3) +DEFAULT_NUM_QUERIES = defaultdict(lambda: 3) +SPLITTER_CHUNK_SIZE = 300 +SPLITTER_OVERLAP = 50 +EMBEDDING_MODEL = "text-embedding-3-large" +EMBEDDING_BATCH_SIZE = 1000 +EMBEDDING_SIZE = 3072 +NUM_NEIGHBORS = 3 +BUFFER_TOKENS = 250 +HTTP_TIMEOUT = 20 +HTTP_MAX_REDIRECTS = 5 +HTTP_MAX_RETIES = 2 + +class Document(BaseModel): + text: str + url: str + embedding: Optional[List[float]] = None + + +URL_QUERY_PROMPT = """ +Here is the user prompt: {USER_PROMPT} + +Please read the prompt carefully and identify the key pieces of information that need to be searched for in order to comprehensively address the topic. + +Brainstorm a list of {NUM_QUERIES} different search queries that cover various aspects of the user prompt. Each query should be focused on a specific sub-topic or question related to the overarching prompt. + +Please write each search query inside its own tags, like this: example search query here + +The queries should be concise while still containing enough information to return relevant search results. Focus the queries on gathering factual information to address the prompt rather than opinions. + +After you have written all {NUM_QUERIES} search queries, please submit your final response. + + +""" + + +PREDICTION_PROMPT = """ +You will be evaluating the likelihood of an event based on a user's question and reasoning provided by another AI. +The user's question is: {USER_INPUT} + +The reasoning from the other AI is: {REASONING} + +Carefully consider the user's question and the provided reasoning. Then, in a , think through the following: + - The probability that the event specified in the user's question will happen (p_yes) + - The probability that the event will not happen (p_no) + - Your confidence level in your prediction + - How useful the reasoning was in helping you make your prediction (info_utility) + +Provide your final scores in the following format: probability between 0 and 1 probability between 0 and 1 +your confidence level between 0 and 1 utility of the reasoning between 0 and 1 + +Remember, p_yes and p_no should add up to 1. Provide your reasoning for each score in the scratchpad before giving your final scores. + +Your response should be structured as follows: + + + + + +""" + + +REASONING_PROMPT = """ +Here is the user's question: {USER_PROMPT} +Here is some additional information that may be relevant to answering the question: {ADDITIONAL_INFOMATION} + +Please carefully read the user's question and the additional information provided. Think through the problem step-by-step, taking into account: + +- The key details from the user's question, such as the specific event they are asking about and the date by which they want to know if it will occur +- Any relevant facts or context provided in the additional information that could help inform your reasoning +- Your own knowledge and analytical capabilities to reason through the likelihood of the event happening by the specified date + +Explain your thought process and show your reasoning for why you believe the event either will or will not occur by the given date. Provide your response inside tags. + +""" + + +MULTI_QUESTIONS_PROMPT = """ +Your task is to take the following user question: +{USER_INPUT} + +Generate 3 different versions of this question that capture different aspects, perspectives or phrasings of the original question. The goal is to help retrieve a broader set of relevant documents from a vector database by providing multiple ways of expressing the user's information need. + +Please provide your output in the following XML tags: + + +[First question version] +[Second question version] +[Third question version] + + +Each question version should aim to surface different keywords, concepts or aspects of the original question while still being relevant to answering the user's core information need. Vary the phrasing and terminology used in each question. However, do not introduce any information that is not implied by the original question. + +""" + + +SYSTEM_PROMPT = """You are a world class algorithm for generating structured output from a given input.""" + + +def parser_query_response(response: str, num_queries: int = 5) -> List[str]: + """Parse the response from the query generation model with optional enhancements.""" + queries = response.split("")[1].split("")[0].split("\n") + parsed_queries = [query.strip() for query in queries if query.strip()] + enhanced_queries = [] + + for query in parsed_queries: + if query[0].isdigit(): + query = ". ".join(query.split(". ")[1:]) + query = query.replace('"', '') + enhanced_queries.append(query) + + if len(enhanced_queries) == num_queries * 2: + enhanced_queries = enhanced_queries[::2] + + # Remove doubel quotes from the queries + final_queries = [query.replace('"', '') for query in enhanced_queries] + + # if there are any xml tags in the queries, remove them + final_queries = [re.sub(r'<[^>]*>', '', query) for query in final_queries] + + return final_queries + + +def parser_multi_questions_response(response: str) -> List[str]: + """Parse the response from the multi questions generation model.""" + questions = response.split("")[1].split("")[0].split("\n") + return [question.strip() for question in questions if question.strip()] + + +def parser_reasoning_response(response: str) -> str: + """Parse the response from the reasoning model.""" + reasoning = response.split("")[1].split("")[0] + return reasoning.strip() + + +def parser_prediction_response(response: str) -> str: + """Parse the response from the prediction model.""" + results = {} + for key in ["p_yes", "p_no", "info_utility", "confidence"]: + try: + value = response.split(f"<{key}>")[1].split(f"")[0].strip() + if key in ["p_yes", "p_no", "info_utility", "confidence"]: + value = float(value) + results[key] = value + except Exception: + raise ValueError(f"Error parsing {key}") + + results = json.dumps(results) + return results + + +def multi_queries( + client: anthropic.Anthropic, + prompt: str, + engine: str, + num_queries: int, + counter_callback: Optional[Callable[[int, int, str], None]] = None, + temperature: Optional[float] = DEFAULT_CLAUDE_SETTINGS["temperature"], + max_tokens: Optional[int] = DEFAULT_CLAUDE_SETTINGS["max_tokens"], +) -> List[str]: + """Generate multiple queries for fetching information from the web.""" + url_query_prompt = URL_QUERY_PROMPT.format( + USER_PROMPT=prompt, NUM_QUERIES=num_queries + ) + + messages = [ + {"role": "user", "content": url_query_prompt}, + ] + + response = client.messages.create( + model=engine, + messages=messages, + system=SYSTEM_PROMPT, + temperature=temperature, + max_tokens=max_tokens, + ) + if counter_callback: + counter_callback( + input_tokens=response.usage.input_tokens, + output_tokens=response.usage.output_tokens, + model=engine, + token_counter=count_tokens, + ) + queries = parser_query_response(response.content[0].text, num_queries=num_queries) + queries.append(prompt) + + return queries, counter_callback + + +def search_google( + query: str, + api_key: str, + engine: str, + num: int +) -> List[str]: + """Search Google for the given query.""" + service = build("customsearch", "v1", developerKey=api_key) + search = ( + service.cse() + .list( + q=query, + cx=engine, + num=num, + ) + .execute() + ) + return [result["link"] for result in search["items"]] + + +def get_urls_from_queries( + queries: List[str], api_key: str, engine: str, num: int +) -> List[str]: + """Get URLs from search engine queries""" + results = [] + for query in queries: + try: + for url in search_google( + query=query, + api_key=api_key, + engine=engine, + num=num, + ): + results.append(url) + except Exception: + pass + unique_results = list(set(results)) + return unique_results + + +def extract_text_from_pdf(url: str, num_words: Optional[int] = None) -> str: + """Extract text from a PDF document at the given URL.""" + try: + response = requests.get(url, timeout=HTTP_TIMEOUT) + response.raise_for_status() + + if "application/pdf" not in response.headers.get("Content-Type", ""): + return ValueError("URL does not point to a PDF document") + + with BytesIO(response.content) as pdf_file: + reader = PyPDF2.PdfReader(pdf_file) + text = "" + for page in reader.pages: + text += page.extract_text() + + doc = Document(text=text[:num_words] if num_words else text, date="", url=url) + print(f"Using PDF: {url}: {doc.text[:300]}...") + return doc + + except Exception as e: + print(f"An error occurred: {e}") + return None + + +def extract_text( + html: str, + num_words: Optional[int] = None, +) -> str: + """Extract text from a single HTML document""" + text = ReadabilityDocument(html).summary() + + # use html2text to convert HTML to markdown + text = md(text, heading_style="ATX") + + if text is None: + return None + + if num_words: + text = " ".join(text.split()[:num_words]) + else: + text = " ".join(text.split()) + + doc = Document(text=text, url="") + return doc + + +def extract_texts(urls: List[str], num_words: Optional[int] = None) -> List[Document]: + """Extract texts from URLs with improved error handling, excluding failed URLs.""" + extracted_texts = [] + for batch in process_in_batches(urls=urls): + for future, url in batch: + if future is None: + continue + try: + result = future.result() + if result.status_code == 200: + # Check if URL ends with .pdf or content starts with %PDF + if url.endswith('.pdf') or result.content[:4] == b'%PDF': + doc = extract_text_from_pdf(url, num_words=num_words) + else: + doc = extract_text(html=result.text, num_words=num_words) + doc.url = url + extracted_texts.append(doc) + except Exception as e: + print(f"Error processing {url}: {e}") + continue + return extracted_texts + + +def process_in_batches( + urls: List[str], + window: int = 5, + timeout: int = HTTP_TIMEOUT, + max_redirects: int = HTTP_MAX_REDIRECTS, + retries: int = HTTP_MAX_RETIES, +) -> Generator[None, None, List[Tuple[Optional[Future], str]]]: + """Iter URLs in batches with improved error handling and retry mechanism.""" + with ThreadPoolExecutor() as executor, requests.Session() as session: + session.max_redirects = max_redirects + for i in range(0, len(urls), window): + batch = urls[i : i + window] + futures = [] + for url in batch: + future = None + attempt = 0 + while attempt < retries: + try: + future = executor.submit(session.get, url, timeout=timeout) + break + except (TooManyRedirects, RequestException) as e: + print(f"Attempt {attempt + 1} failed for {url}: {e}") + attempt += 1 + if attempt == retries: + print(f"Max retries reached for {url}. Moving to next URL.") + futures.append((future, url)) + yield futures + + +def recursive_character_text_splitter(text, max_tokens, overlap): + if len(text) <= max_tokens: + return [text] + else: + return [ + text[i : i + max_tokens] for i in range(0, len(text), max_tokens - overlap) + ] + + +def get_embeddings( + client: OpenAI, + split_docs: List[Document] +) -> List[Document]: + """Get embeddings for the split documents.""" + for batch_start in range(0, len(split_docs), EMBEDDING_BATCH_SIZE): + batch_end = batch_start + EMBEDDING_BATCH_SIZE + batch = [doc.text for doc in split_docs[batch_start:batch_end]] + response = client.embeddings.create( + model=EMBEDDING_MODEL, + input=batch, + ) + for i, be in enumerate(response.data): + assert i == be.index + batch_embeddings = [e.embedding for e in response.data] + for i, doc in enumerate(split_docs[batch_start:batch_end]): + doc.embedding = batch_embeddings[i] + return split_docs + + +def find_similar_chunks( + client: OpenAI, + query: str, + docs_with_embeddings: List[Document], + k: int = 4 +) -> List: + """Similarity search to find similar chunks to a query""" + query_embedding = ( + client.embeddings.create( + model=EMBEDDING_MODEL, + input=query, + ) + .data[0] + .embedding + ) + + index = faiss.IndexFlatIP(EMBEDDING_SIZE) + index.add(np.array([doc.embedding for doc in docs_with_embeddings])) + D, I = index.search(np.array([query_embedding]), k) + + return [docs_with_embeddings[i] for i in I[0]] + + +def multi_questions_response( + client: anthropic.Anthropic, + prompt:str, + engine:str, + temperature:float = DEFAULT_CLAUDE_SETTINGS["temperature"], + max_tokens:int = DEFAULT_CLAUDE_SETTINGS["max_tokens"], + counter_callback: Optional[Callable[[int, int, str], None]] = None, +) -> List[str]: + """Generate multiple questions for fetching information from the web.""" + try: + multi_questions_prompt = MULTI_QUESTIONS_PROMPT.format(USER_INPUT=prompt) + messages = [ + {"role": "user", "content": multi_questions_prompt}, + ] + + response = client.messages.create( + model=engine, + messages=messages, + system=SYSTEM_PROMPT, + temperature=temperature, + max_tokens=max_tokens, + ) + + if counter_callback: + counter_callback( + input_tokens=response.usage.input_tokens, + output_tokens=response.usage.output_tokens, + model=engine, + token_counter=count_tokens, + ) + + # append the user's question to the list of questions + multi_questions = parser_multi_questions_response(response.content[0].text) + multi_questions.append(prompt) + + return multi_questions, counter_callback + + except Exception as e: + print(f"Error generating multiple questions: {e}") + return [prompt], counter_callback + + +def reciprocal_rank_refusion(similar_chunks: List[Document], k: int) -> List[Document]: + """Reciprocal rank refusion to re-rank the similar chunks based on the text.""" + fused_chunks = {} + for rank, doc in enumerate(similar_chunks): + doc_text = doc.text + if doc_text not in fused_chunks: + fused_chunks[doc_text] = (doc, 0) + fused_chunks[doc_text] = (doc, fused_chunks[doc_text][1] + 1 / (rank + 60)) + + sorted_fused_chunks = sorted(fused_chunks.values(), key=lambda x: x[1], reverse=True) + + return [doc for doc, _ in sorted_fused_chunks[:k]] + + +def count_tokens(text: str, model: str) -> int: + """Count the number of tokens in a text.""" + enc = encoding_for_model(model) + return len(enc.encode(text)) + + +def fetch_additional_information( + client: anthropic.Anthropic, + client_openai: OpenAI, + prompt: str, + engine: str, + google_api_key: Optional[str], + google_engine_id: Optional[str], + counter_callback: Optional[Callable[[int, int, str], None]] = None, + source_links: Optional[List[str]] = None, + num_urls: Optional[int] = DEFAULT_NUM_URLS, + num_queries: Optional[int] = DEFAULT_NUM_QUERIES, + temperature: Optional[float] = DEFAULT_CLAUDE_SETTINGS["temperature"], + max_tokens: Optional[int] = DEFAULT_CLAUDE_SETTINGS["max_tokens"] +) -> Tuple[str, List[str], Optional[Callable[[int, int, str], None]]]: + """Fetch additional information from the web.""" + + # generate multiple queries for fetching information from the web + try: + queries, counter_callback = multi_queries( + client=client, + prompt=prompt, + engine=engine, + num_queries=num_queries, + counter_callback=counter_callback, + temperature=temperature, + max_tokens=max_tokens, + ) + print(f"Queries: {queries}") + except Exception as e: + print(f"Error generating queries: {e}") + queries = [prompt] + + # get the top URLs for the queries + if not source_links: + urls = get_urls_from_queries( + queries=queries, + api_key=google_api_key, + engine=google_engine_id, + num=num_urls, + ) + print(f"URLs: {urls}") + + urls = list(set(urls)) + + # Extract text and dates from the URLs + docs = extract_texts( + urls=urls, + ) + else: + docs = [] + for url, content in islice(source_links.items(), num_urls or len(source_links)): + doc = extract_text(html=content) + doc.url = url + docs.append(doc) + + # Remove None values from the list + docs = [doc for doc in docs if doc] + + # remove empty documents with "" + filtered_docs = [doc for doc in docs if hasattr(doc, "text") and doc.text != ""] + + # Chunk the documents + split_docs = [] + for doc in filtered_docs: + try: + t = recursive_character_text_splitter( + doc.text, SPLITTER_CHUNK_SIZE, SPLITTER_OVERLAP + ) + split_docs.extend( + [Document(text=chunk, url=doc.url) for chunk in t] + ) + except Exception as e: + print(f"Error splitting document: {e}") + continue + print(f"Split Docs: {len(split_docs)}") + + # Remove None values from the list + split_docs = [doc for doc in split_docs if doc] + + # Embed the documents + docs_with_embeddings = get_embeddings(client_openai, split_docs) + print(f"Docs with embeddings: {len(docs_with_embeddings)}") + + # multi questions prompt + questions, counter_callback = multi_questions_response( + client=client, + prompt=prompt, + engine=engine, + counter_callback=counter_callback, + temperature=temperature, + max_tokens=max_tokens, + ) + print(f"Questions: {questions}") + + similar_chunks = [] + for question in questions: + similar_chunks.extend(find_similar_chunks( + client=client_openai, + query=question, + docs_with_embeddings=docs_with_embeddings, + k=NUM_NEIGHBORS, + )) + print(f"Similar Chunks before refusion: {len(similar_chunks)}") + + # Reciprocal rank refusion + similar_chunks = reciprocal_rank_refusion(similar_chunks, NUM_NEIGHBORS) + print(f"Similar Chunks after refusion: {len(similar_chunks)}") + + # Format the additional information + additional_information = "\n".join( + [ + f"ARTICLE {i}, URL: {doc.url}, CONTENT: {doc.text}\n" + for i, doc in enumerate(similar_chunks) + ] + ) + + return additional_information, queries, counter_callback + + +def extract_question(prompt: str) -> str: + pattern = r'\"(.*?)\"' + try: + question = re.findall(pattern, prompt)[0] + except Exception as e: + print(f"Error extracting question: {e}") + question = prompt + + return question + + +def run(**kwargs) -> Tuple[str, Optional[str], Optional[Dict[str, Any]], Any]: + """Run the task""" + tool = kwargs["tool"] + model = kwargs.get("model", TOOL_TO_ENGINE[tool]) + prompt = extract_question(kwargs["prompt"]) + max_tokens = kwargs.get("max_tokens", DEFAULT_CLAUDE_SETTINGS["max_tokens"]) + temperature = kwargs.get("temperature", DEFAULT_CLAUDE_SETTINGS["temperature"]) + num_urls = kwargs.get("num_urls", DEFAULT_NUM_URLS[tool]) + num_queries = kwargs.get("num_queries", DEFAULT_NUM_QUERIES[tool]) + counter_callback = kwargs.get("counter_callback", None) + api_keys = kwargs.get("api_keys", {}) + google_api_key = api_keys.get("google_api_key", None) + google_engine_id = api_keys.get("google_engine_id", None) + client = anthropic.Anthropic(api_key=api_keys["anthropic"]) + client_openai = OpenAI(api_key=api_keys["openai"]) + + + # Make sure the model is supported + if model not in ALLOWED_MODELS: + raise ValueError(f"Model {model} not supported.") + + # make sure the tool is supported + if tool not in ALLOWED_TOOLS: + raise ValueError(f"Tool {tool} not supported.") + + engine = kwargs.get("model", TOOL_TO_ENGINE[tool]) + print(f"ENGINE: {engine}") + + ( + additional_information, + queries, + counter_callback, + ) = fetch_additional_information( + client=client, + client_openai=client_openai, + prompt=prompt, + engine=engine, + google_api_key=google_api_key, + google_engine_id=google_engine_id, + counter_callback=counter_callback, + source_links=kwargs.get("source_links", None), + num_urls=num_urls, + num_queries=num_queries, + temperature=temperature, + max_tokens=max_tokens, + ) + + # Reasoning prompt + reasoning_prompt = REASONING_PROMPT.format( + USER_PROMPT=prompt, ADDITIONAL_INFOMATION=additional_information + ) + + # Do reasoning + messages = [ + { + "role": "user", + "content": reasoning_prompt, + }, + ] + + # Reasoning + response_reasoning = client.messages.create( + model=engine, + messages=messages, + system=SYSTEM_PROMPT, + temperature=temperature, + max_tokens=max_tokens, + ) + + if counter_callback: + counter_callback( + input_tokens=response_reasoning.usage.input_tokens, + output_tokens=response_reasoning.usage.output_tokens, + model=engine, + token_counter=count_tokens, + ) + + # Extract the reasoning + reasoning = parser_reasoning_response(response_reasoning.content[0].text) + + # Prediction prompt + prediction_prompt = PREDICTION_PROMPT.format( + USER_INPUT=prompt, REASONING=reasoning + ) + + # Make the prediction + messages = [ + { + "role": "user", + "content": prediction_prompt, + }, + ] + + response_prediction = client.messages.create( + model=engine, + messages=messages, + system=SYSTEM_PROMPT, + temperature=temperature, + max_tokens=max_tokens, + ) + prediction = parser_prediction_response(response_prediction.content[0].text) + + if counter_callback: + counter_callback( + input_tokens=response_prediction.usage.input_tokens, + output_tokens=response_prediction.usage.output_tokens, + model=engine, + token_counter=count_tokens, + ) + + return prediction, reasoning_prompt + "////" + prediction_prompt, None, counter_callback diff --git a/packages/napthaai/customs/prediction_url_cot_claude/__init__.py b/packages/napthaai/customs/prediction_url_cot_claude/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/napthaai/customs/prediction_url_cot_claude/prediction_url_cot_claude.py b/packages/napthaai/customs/prediction_url_cot_claude/prediction_url_cot_claude.py new file mode 100644 index 00000000..4202bad4 --- /dev/null +++ b/packages/napthaai/customs/prediction_url_cot_claude/prediction_url_cot_claude.py @@ -0,0 +1,521 @@ +from collections import defaultdict +from concurrent.futures import Future, ThreadPoolExecutor +from docstring_parser import parse +from googleapiclient.discovery import build +from itertools import islice +import json +import re +from io import BytesIO +import PyPDF2 +import anthropic +from pydantic import BaseModel, Field +from readability import Document as ReadabilityDocument +import requests +from requests.exceptions import RequestException, TooManyRedirects +from markdownify import markdownify as md +from typing import Any, Dict, Generator, List, Optional, Tuple, Callable +from tiktoken import encoding_for_model + + +DEFAULT_CLAUDE_SETTINGS = { + "max_tokens": 1000, + "temperature": 0, +} +MAX_TOKENS = { + 'claude-2': 200_0000, + 'claude-2.1': 200_0000, + 'claude-3-haiku-20240307': 200_0000, + 'claude-3-sonnet-20240229': 200_0000, + 'claude-3-opus-20240229': 200_0000, +} +ALLOWED_TOOLS = [ + "prediction-url-cot-claude", +] +ALLOWED_MODELS = [ + "claude-2", + "claude-2.1", + "claude-3-haiku-20240307", + "claude-3-sonnet-20240229", + "claude-3-opus-20240229", +] +TOOL_TO_ENGINE = {tool: "claude-3-haiku-20240307" for tool in ALLOWED_TOOLS} +NUM_QUERIES = 5 +NUM_URLS_PER_QUERY = 3 +HTTP_TIMEOUT = 20 +HTTP_MAX_REDIRECTS = 5 +HTTP_MAX_RETIES = 2 +MIN_WORDS = 100 +MAX_DOC_WORDS = 10000 +N_DOCS = 5 + +PREDICTION_PROMPT = """ +Here is some additional background information that may be relevant to the question: + {ADDITIONAL_INFORMATION} + +A user has asked the following: + + {USER_PROMPT} + +Carefully consider the user's question and the additional information provided. Think through the likelihood of the event the user asked about actually happening in the future, based on the details given. Write out your reasoning and analysis in a section. + +Now, based on your analysis above, provide a prediction of the probability the event will happen, as p_yes between 0 and 1. Also provide the probability it will not happen, as p_no between 0 and 1. The two probabilities should sum to 1. + +p_yes: p_no: + +How useful was the additional information in allowing you to make a prediction? Provide your rating as info_utility, a number between 0 and 1. + +info_utility: + +Finally, considering everything, what is your overall confidence in your prediction? Provide your confidence as a number between 0 and 1. + +confidence: + +Make sure the values you provide are between 0 and 1. And p_yes and p_no should sum to 1. + +Your response should be structured as follows: + + + + + +""" + + +URL_QUERY_PROMPT = """ +Here is the user prompt: {USER_PROMPT} + +Please read the prompt carefully and identify the key pieces of information that need to be searched for in order to comprehensively address the topic. + +Brainstorm a list of {NUM_QUERIES} different search queries that cover various aspects of the user prompt. Each query should be focused on a specific sub-topic or question related to the overarching prompt. + +Please write each search query inside its own tags, like this: example search query here + +The queries should be concise while still containing enough information to return relevant search results. Focus the queries on gathering factual information to address the prompt rather than opinions. + +After you have written all {NUM_QUERIES} search queries, please submit your final response. + + +""" + +SYSTEM_PROMPT = """You are a world class algorithm for generating structured output from a given input.""" + + +class Document(BaseModel): + text: str + url: str + embedding: Optional[List[float]] = None + + +def count_tokens(text: str, model: str) -> int: + """Count the number of tokens in a text.""" + enc = encoding_for_model(model) + return len(enc.encode(text)) + + +def multi_queries( + client: anthropic.Anthropic, + prompt: str, + engine: str, + num_queries: int, + counter_callback: Optional[Callable[[int, int, str], None]] = None, + temperature: Optional[float] = DEFAULT_CLAUDE_SETTINGS["temperature"], + max_tokens: Optional[int] = DEFAULT_CLAUDE_SETTINGS["max_tokens"], +) -> List[str]: + """Generate multiple queries for fetching information from the web.""" + url_query_prompt = URL_QUERY_PROMPT.format( + USER_PROMPT=prompt, NUM_QUERIES=num_queries + ) + + messages = [ + {"role": "user", "content": url_query_prompt}, + ] + + response = client.messages.create( + model=engine, + messages=messages, + system=SYSTEM_PROMPT, + temperature=temperature, + max_tokens=max_tokens, + ) + if counter_callback: + counter_callback( + input_tokens=response.usage.input_tokens, + output_tokens=response.usage.output_tokens, + model=engine, + token_counter=count_tokens, + ) + queries = parser_query_response(response.content[0].text, num_queries=num_queries) + queries.append(prompt) + + return queries, counter_callback + + +def parser_query_response(response: str, num_queries: int = 5) -> List[str]: + """Parse the response from the query generation model with optional enhancements.""" + queries = response.split("")[1].split("")[0].split("\n") + parsed_queries = [query.strip() for query in queries if query.strip()] + enhanced_queries = [] + + for query in parsed_queries: + if query[0].isdigit(): + query = ". ".join(query.split(". ")[1:]) + query = query.replace('"', '') + enhanced_queries.append(query) + + if len(enhanced_queries) == num_queries * 2: + enhanced_queries = enhanced_queries[::2] + + # Remove doubel quotes from the queries + final_queries = [query.replace('"', '') for query in enhanced_queries] + + # if there are any xml tags in the queries, remove them + final_queries = [re.sub(r'<[^>]*>', '', query) for query in final_queries] + + return final_queries + + +def search_google( + query: str, + api_key: str, + engine: str, + num: int +) -> List[str]: + """Search Google for the given query.""" + service = build("customsearch", "v1", developerKey=api_key) + search = ( + service.cse() + .list( + q=query, + cx=engine, + num=num, + ) + .execute() + ) + return [result["link"] for result in search["items"]] + + +def get_urls_from_queries( + queries: List[str], api_key: str, engine: str, num: int +) -> List[str]: + """Get URLs from search engine queries""" + results = [] + for query in queries: + try: + for url in search_google( + query=query, + api_key=api_key, + engine=engine, + num=num, + ): + results.append(url) + except Exception: + pass + unique_results = list(set(results)) + return unique_results + + +def extract_text( + html: str, + num_words: Optional[int] = None, +) -> str: + """Extract text from a single HTML document""" + text = ReadabilityDocument(html).summary() + + # use html2text to convert HTML to markdown + text = md(text, heading_style="ATX") + + if text is None: + return None + + if num_words: + text = " ".join(text.split()[:num_words]) + else: + text = " ".join(text.split()) + + doc = Document(text=text, url="") + return doc + + +def extract_text_from_pdf(url: str, num_words: Optional[int] = None) -> str: + """Extract text from a PDF document at the given URL.""" + try: + response = requests.get(url, timeout=HTTP_TIMEOUT) + response.raise_for_status() + + if "application/pdf" not in response.headers.get("Content-Type", ""): + return ValueError("URL does not point to a PDF document") + + with BytesIO(response.content) as pdf_file: + reader = PyPDF2.PdfReader(pdf_file) + text = "" + for page in reader.pages: + text += page.extract_text() + + doc = Document(text=text[:num_words] if num_words else text, date="", url=url) + print(f"Using PDF: {url}: {doc.text[:300]}...") + return doc + + except Exception as e: + print(f"An error occurred: {e}") + return None + + +def process_in_batches( + urls: List[str], + window: int = 5, + timeout: int = HTTP_TIMEOUT, + max_redirects: int = HTTP_MAX_REDIRECTS, + retries: int = HTTP_MAX_RETIES, +) -> Generator[None, None, List[Tuple[Optional[Future], str]]]: + """Iter URLs in batches with improved error handling and retry mechanism.""" + with ThreadPoolExecutor() as executor, requests.Session() as session: + session.max_redirects = max_redirects + for i in range(0, len(urls), window): + batch = urls[i : i + window] + futures = [] + for url in batch: + future = None + attempt = 0 + while attempt < retries: + try: + future = executor.submit(session.get, url, timeout=timeout) + break + except (TooManyRedirects, RequestException) as e: + print(f"Attempt {attempt + 1} failed for {url}: {e}") + attempt += 1 + if attempt == retries: + print(f"Max retries reached for {url}. Moving to next URL.") + futures.append((future, url)) + yield futures + + +def extract_texts(urls: List[str], num_words: Optional[int] = None) -> List[Document]: + """Extract texts from URLs with improved error handling, excluding failed URLs.""" + extracted_texts = [] + for batch in process_in_batches(urls=urls): + for future, url in batch: + if future is None: + continue + try: + result = future.result() + if result.status_code == 200: + # Check if URL ends with .pdf or content starts with %PDF + if url.endswith('.pdf') or result.content[:4] == b'%PDF': + doc = extract_text_from_pdf(url, num_words=num_words) + else: + doc = extract_text(html=result.text, num_words=num_words) + doc.url = url + extracted_texts.append(doc) + except Exception as e: + print(f"Error processing {url}: {e}") + continue + return extracted_texts + + +def extract_question(prompt: str) -> str: + pattern = r'\"(.*?)\"' + try: + question = re.findall(pattern, prompt)[0] + except Exception as e: + question = prompt + + return question + +def count_words(text: str) -> int: + """Count the number of words in a text.""" + return len(text.split()) + +def select_docs( + docs: List[Document], + n_docs: int, + min_words: int = MIN_WORDS, + max_words: int = MAX_DOC_WORDS, +) -> List[Document]: + """Select N documents from the list.""" + + word_counts = {doc.url: count_words(doc.text) for doc in docs} + + # Sort the documents by word count + sorted_docs = sorted(word_counts, key=word_counts.get, reverse=True) + + selected_urls = [] + for u in sorted_docs: + if word_counts[u] >= 60: + selected_urls.append(u) + if len(selected_urls) == 4: + break + + # selected urls to doc + selected_docs = [doc for doc in docs if doc.url in selected_urls] + + # make sure the selected documents are less than 10000 words + for doc in selected_docs: + if word_counts[doc.url] > 10000: + doc.text = " ".join(doc.text.split()[:10000]) + + return selected_docs + +def fetch_additional_information( + client: anthropic.Anthropic, + prompt: str, + engine: str, + google_api_key: Optional[str], + google_engine_id: Optional[str], + counter_callback: Optional[Callable[[int, int, str], None]] = None, + source_links: Optional[List[str]] = None, + num_urls: Optional[int] = NUM_URLS_PER_QUERY, + num_queries: Optional[int] = NUM_QUERIES, + temperature: Optional[float] = DEFAULT_CLAUDE_SETTINGS["temperature"], + max_tokens: Optional[int] = DEFAULT_CLAUDE_SETTINGS["max_tokens"], + n_docs: int = N_DOCS, +) -> Tuple[str, Callable[[int, int, str], None]]: + """Fetch additional information from the web.""" + + # generate multiple queries for fetching information from the web + try: + queries, counter_callback = multi_queries( + client=client, + prompt=prompt, + engine=engine, + num_queries=num_queries, + counter_callback=counter_callback, + temperature=temperature, + max_tokens=max_tokens, + ) + print(f"Queries: {queries}") + except Exception as e: + print(f"Error generating queries: {e}") + queries = [prompt] + + # get the top URLs for the queries + if not source_links: + urls = get_urls_from_queries( + queries=queries, + api_key=google_api_key, + engine=google_engine_id, + num=NUM_URLS_PER_QUERY, + ) + print(f"URLs: {urls}") + + urls = list(set(urls)) + + # Extract text and dates from the URLs + docs = extract_texts( + urls=urls, + ) + else: + docs = [] + for url, content in islice(source_links.items(), num_urls or len(source_links)): + doc = extract_text(html=content) + doc.url = url + docs.append(doc) + + # Remove None values from the list + docs = [doc for doc in docs if doc] + + # remove empty documents "" + filtered_docs = [doc for doc in docs if hasattr(doc, 'text') and doc.text != ""] + + # Select N urls to be used + selected_docs = select_docs(filtered_docs, n_docs) + + # Format the additional information + additional_information = "\n".join( + [ + f"ARTICLE {i}, URL: {doc.url}, CONTENT: {doc.text}\n" + for i, doc in enumerate(selected_docs) + ] + ) + + return additional_information, counter_callback + + +def parser_prediction_response(response: str) -> str: + """Parse the response from the prediction model.""" + results = {} + for key in ["p_yes", "p_no", "info_utility", "confidence"]: + try: + value = response.split(f"<{key}>")[1].split(f"")[0].strip() + if key in ["p_yes", "p_no", "info_utility", "confidence"]: + value = float(value) + results[key] = value + except Exception: + raise ValueError(f"Error parsing {key}") + + results = json.dumps(results) + return results + + +def run(**kwargs) -> Tuple[Optional[str], Any, Optional[Dict[str, Any]], Any]: + """Run the task""" + + tool = kwargs["tool"] + model = kwargs.get("model", TOOL_TO_ENGINE[tool]) + prompt = extract_question(kwargs["prompt"]) + max_tokens = kwargs.get("max_tokens", DEFAULT_CLAUDE_SETTINGS["max_tokens"]) + temperature = kwargs.get("temperature", DEFAULT_CLAUDE_SETTINGS["temperature"]) + num_urls = kwargs.get("num_urls", NUM_URLS_PER_QUERY) + num_queries = kwargs.get("num_queries", NUM_QUERIES) + n_docs = kwargs.get("n_docs", N_DOCS) + counter_callback = kwargs.get("counter_callback", None) + api_keys = kwargs.get("api_keys", {}) + google_api_key = api_keys.get("google_api_key", None) + google_engine_id = api_keys.get("google_engine_id", None) + client = anthropic.Anthropic(api_key=api_keys["anthropic"]) + + # Make sure the model is supported + if model not in ALLOWED_MODELS: + raise ValueError(f"Model {model} not supported.") + + # make sure the tool is supported + if tool not in ALLOWED_TOOLS: + raise ValueError(f"Tool {tool} not supported.") + + engine = kwargs.get("model", TOOL_TO_ENGINE[tool]) + print(f"ENGINE: {engine}") + + additional_information, counter_callback = fetch_additional_information( + client=client, + prompt=prompt, + engine=engine, + google_api_key=google_api_key, + google_engine_id=google_engine_id, + counter_callback=counter_callback, + source_links=kwargs.get("source_links", None), + num_urls=num_urls, + num_queries=num_queries, + temperature=temperature, + max_tokens=max_tokens, + n_docs=n_docs, + ) + + # Generate the prediction prompt + prediction_prompt = PREDICTION_PROMPT.format( + ADDITIONAL_INFORMATION=additional_information, + USER_PROMPT=prompt, + ) + + # Generate the prediction + messages = [ + {"role": "user", "content": prediction_prompt}, + ] + + response = client.messages.create( + model=engine, + messages=messages, + system=SYSTEM_PROMPT, + temperature=temperature, + max_tokens=max_tokens, + ) + + if counter_callback: + counter_callback( + input_tokens=response.usage.input_tokens, + output_tokens=response.usage.output_tokens, + model=engine, + token_counter=count_tokens, + ) + + results = parser_prediction_response(response.content[0].text) + + return results, prediction_prompt, None, counter_callback diff --git a/packages/packages.json b/packages/packages.json index bf9625fa..e118d2f7 100644 --- a/packages/packages.json +++ b/packages/packages.json @@ -1,7 +1,7 @@ { "dev": { "custom/valory/native_transfer_request/0.1.0": "bafybeid22vi5xtavqhq5ir2kq6nakckm3tl72wcgftsq35ak3cboyn6eea", - "custom/valory/prediction_request_claude/0.1.0": "bafybeidmtovzewf3be6wzdsoozdyin2hvq2efw233arohv243f52jzapli", + "custom/valory/prediction_request_claude/0.1.0": "bafybeigpuw4z2xq6vxdsi27cwqomdvbbz364sgzzzkofjxowmd6shwrcoa", "custom/valory/openai_request/0.1.0": "bafybeigew6ukd53n3z352wmr5xu6or3id7nsqn7vb47bxs4pg4qtkmbdiu", "custom/valory/prediction_request_embedding/0.1.0": "bafybeifdhbbxmwf4q6pjhanubgrzhy7hetupyoekjyxvnubcccqxlkaqu4", "custom/valory/resolve_market/0.1.0": "bafybeiaag2e7rsdr3bwg6mlmfyom4vctsdapohco7z45pxhzjymepz3rya", diff --git a/packages/valory/customs/prediction_request_claude/component.yaml b/packages/valory/customs/prediction_request_claude/component.yaml index beef306f..cf2a798b 100644 --- a/packages/valory/customs/prediction_request_claude/component.yaml +++ b/packages/valory/customs/prediction_request_claude/component.yaml @@ -7,7 +7,7 @@ license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: bafybeibbn67pnrrm4qm3n3kbelvbs3v7fjlrjniywmw2vbizarippidtvi - prediction_request_claude.py: bafybeihwoeu7vhwqibzif46mtgw4jgbiphp3qfo3da4aeqe4nnbrzs3bqu + prediction_request_claude.py: bafybeievmbi5ck63om47nimumdqzflhcpqkqymmtauomoouszh2gs23coi fingerprint_ignore_patterns: [] entry_point: prediction_request_claude.py callable: run diff --git a/packages/valory/customs/prediction_request_claude/prediction_request_claude.py b/packages/valory/customs/prediction_request_claude/prediction_request_claude.py index 6e76050d..e5ba55b3 100644 --- a/packages/valory/customs/prediction_request_claude/prediction_request_claude.py +++ b/packages/valory/customs/prediction_request_claude/prediction_request_claude.py @@ -247,7 +247,14 @@ def fetch_additional_information( prompt=url_query_prompt, stop_sequences=STOP_SEQUENCES, ) - json_data = json.loads(completion.completion) + try: + json_data = json.loads(completion.completion) + except json.JSONDecodeError: + json_data = {} + + if "queries" not in json_data: + json_data["queries"] = [prompt] + if not source_links: urls = get_urls_from_queries( json_data["queries"], diff --git a/poetry.lock b/poetry.lock index 6e74b662..38037536 100644 --- a/poetry.lock +++ b/poetry.lock @@ -123,22 +123,27 @@ files = [ [[package]] name = "anthropic" -version = "0.3.11" -description = "Client library for the anthropic API" +version = "0.21.3" +description = "The official Python library for the anthropic API" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.7" files = [ - {file = "anthropic-0.3.11-py3-none-any.whl", hash = "sha256:5c81105cd9ee7388bff3fdb739aaddedc83bbae9b95d51c2d50c13b1ad106138"}, - {file = "anthropic-0.3.11.tar.gz", hash = "sha256:2e0fa5351c9b368cbed0bbd7217deaa9409b82b56afaf244e2196e99eb4fe20e"}, + {file = "anthropic-0.21.3-py3-none-any.whl", hash = "sha256:5869115453b543a46ded6515c9f29b8d610b6e94bbba3230ad80ac947d2b0862"}, + {file = "anthropic-0.21.3.tar.gz", hash = "sha256:02f1ab5694c497e2b2d42d30d51a4f2edcaca92d2ec86bb64fe78a9c7434a869"}, ] [package.dependencies] -anyio = ">=3.5.0,<4" +anyio = ">=3.5.0,<5" distro = ">=1.7.0,<2" httpx = ">=0.23.0,<1" pydantic = ">=1.9.0,<3" +sniffio = "*" tokenizers = ">=0.13.0" -typing-extensions = ">=4.5,<5" +typing-extensions = ">=4.7,<5" + +[package.extras] +bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] +vertex = ["google-auth (>=2,<3)"] [[package]] name = "anyio" @@ -1406,18 +1411,18 @@ files = [ [[package]] name = "fastapi" -version = "0.110.0" +version = "0.110.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.110.0-py3-none-any.whl", hash = "sha256:87a1f6fb632a218222c5984be540055346a8f5d8a68e8f6fb647b1dc9934de4b"}, - {file = "fastapi-0.110.0.tar.gz", hash = "sha256:266775f0dcc95af9d3ef39bad55cff525329a931d5fd51930aadd4f428bf7ff3"}, + {file = "fastapi-0.110.1-py3-none-any.whl", hash = "sha256:5df913203c482f820d31f48e635e022f8cbfe7350e4830ef05a3163925b1addc"}, + {file = "fastapi-0.110.1.tar.gz", hash = "sha256:6feac43ec359dfe4f45b2c18ec8c94edb8dc2dfc461d417d9e626590c071baad"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.36.3,<0.37.0" +starlette = ">=0.37.2,<0.38.0" typing-extensions = ">=4.8.0" [package.extras] @@ -2006,13 +2011,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "huggingface-hub" -version = "0.22.1" +version = "0.22.2" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.22.1-py3-none-any.whl", hash = "sha256:eac63947923d15c9a68681d7ed2d9599e058860617064e3ee6bd91a4b954faaf"}, - {file = "huggingface_hub-0.22.1.tar.gz", hash = "sha256:5b8aaee5f3618cd432f49886da9935bbe8fab92d719011826430907b93171dd8"}, + {file = "huggingface_hub-0.22.2-py3-none-any.whl", hash = "sha256:3429e25f38ccb834d310804a3b711e7e4953db5a9e420cc147a5e194ca90fd17"}, + {file = "huggingface_hub-0.22.2.tar.gz", hash = "sha256:32e9a9a6843c92f253ff9ca16b9985def4d80a93fb357af5353f770ef74a81be"}, ] [package.dependencies] @@ -2383,124 +2388,165 @@ test = ["pytest"] [[package]] name = "lxml" -version = "5.2.0" +version = "5.2.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" files = [ - {file = "lxml-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c54f8d6160080831a76780d850302fdeb0e8d0806f661777b0714dfb55d9a08a"}, - {file = "lxml-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e95ae029396382a0d2e8174e4077f96befcd4a2184678db363ddc074eb4d3b2"}, - {file = "lxml-5.2.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5810fa80e64a0c689262a71af999c5735f48c0da0affcbc9041d1ef5ef3920be"}, - {file = "lxml-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae69524fd6a68b288574013f8fadac23cacf089c75cd3fc5b216277a445eb736"}, - {file = "lxml-5.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fadda215e32fe375d65e560b7f7e2a37c7f9c4ecee5315bb1225ca6ac9bf5838"}, - {file = "lxml-5.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:f1f164e4cc6bc646b1fc86664c3543bf4a941d45235797279b120dc740ee7af5"}, - {file = "lxml-5.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3603a8a41097daf7672cae22cc4a860ab9ea5597f1c5371cb21beca3398b8d6a"}, - {file = "lxml-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3b4bb89a785f4fd60e05f3c3a526c07d0d68e3536f17f169ca13bf5b5dd75a5"}, - {file = "lxml-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1effc10bf782f0696e76ecfeba0720ea02c0c31d5bffb7b29ba10debd57d1c3d"}, - {file = "lxml-5.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b03531f6cd6ce4b511dcece060ca20aa5412f8db449274b44f4003f282e6272f"}, - {file = "lxml-5.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fac15090bb966719df06f0c4f8139783746d1e60e71016d8a65db2031ca41b8"}, - {file = "lxml-5.2.0-cp310-cp310-win32.whl", hash = "sha256:92bb37c96215c4b2eb26f3c791c0bf02c64dd251effa532b43ca5049000c4478"}, - {file = "lxml-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:b0181c22fdb89cc19e70240a850e5480817c3e815b1eceb171b3d7a3aa3e596a"}, - {file = "lxml-5.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ada8ce9e6e1d126ef60d215baaa0c81381ba5841c25f1d00a71cdafdc038bd27"}, - {file = "lxml-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cefb133c859f06dab2ae63885d9f405000c4031ec516e0ed4f9d779f690d8e3"}, - {file = "lxml-5.2.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ede2a7a86a977b0c741654efaeca0af7860a9b1ae39f9268f0936246a977ee0"}, - {file = "lxml-5.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d46df6f0b1a0cda39d12c5c4615a7d92f40342deb8001c7b434d7c8c78352e58"}, - {file = "lxml-5.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2259243ee734cc736e237719037efb86603c891fd363cc7973a2d0ac8a0e3f"}, - {file = "lxml-5.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c53164f29ed3c3868787144e8ea8a399ffd7d8215f59500a20173593c19e96eb"}, - {file = "lxml-5.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:371aab9a397dcc76625ad3b02fa9b21be63406d69237b773156e7d1fc2ce0cae"}, - {file = "lxml-5.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e08784288a179b59115b5e57abf6d387528b39abb61105fe17510a199a277a40"}, - {file = "lxml-5.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c232726f7b6df5143415a06323faaa998ef8abbe1c0ed00d718755231d76f08"}, - {file = "lxml-5.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e4366e58c0508da4dee4c7c70cee657e38553d73abdffa53abbd7d743711ee11"}, - {file = "lxml-5.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c84dce8fb2e900d4fb094e76fdad34a5fd06de53e41bddc1502c146eb11abd74"}, - {file = "lxml-5.2.0-cp311-cp311-win32.whl", hash = "sha256:0947d1114e337dc2aae2fa14bbc9ed5d9ca1a0acd6d2f948df9926aef65305e9"}, - {file = "lxml-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1eace37a9f4a1bef0bb5c849434933fd6213008ec583c8e31ee5b8e99c7c8500"}, - {file = "lxml-5.2.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f2cb157e279d28c66b1c27e0948687dc31dc47d1ab10ce0cd292a8334b7de3d5"}, - {file = "lxml-5.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:53c0e56f41ef68c1ce4e96f27ecdc2df389730391a2fd45439eb3facb02d36c8"}, - {file = "lxml-5.2.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703d60e59ab45c17485c2c14b11880e4f7f0eab07134afa9007573fa5a779a5a"}, - {file = "lxml-5.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaf5e308a5e50bc0548c4fdca0117a31ec9596f8cfc96592db170bcecc71a957"}, - {file = "lxml-5.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af64df85fecd3cf3b2e792f0b5b4d92740905adfa8ce3b24977a55415f1a0c40"}, - {file = "lxml-5.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:df7dfbdef11702fd22c2eaf042d7098d17edbc62d73f2199386ad06cbe466f6d"}, - {file = "lxml-5.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7250030a7835bfd5ba6ca7d1ad483ec90f9cbc29978c5e75c1cc3e031d3c4160"}, - {file = "lxml-5.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:be5faa2d5c8c8294d770cfd09d119fb27b5589acc59635b0cf90f145dbe81dca"}, - {file = "lxml-5.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:347ec08250d5950f5b016caa3e2e13fb2cb9714fe6041d52e3716fb33c208663"}, - {file = "lxml-5.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dc7b630c4fb428b8a40ddd0bfc4bc19de11bb3c9b031154f77360e48fe8b4451"}, - {file = "lxml-5.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ae550cbd7f229cdf2841d9b01406bcca379a5fb327b9efb53ba620a10452e835"}, - {file = "lxml-5.2.0-cp312-cp312-win32.whl", hash = "sha256:7c61ce3cdd6e6c9f4003ac118be7eb3036d0ce2afdf23929e533e54482780f74"}, - {file = "lxml-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f90c36ca95a44d2636bbf55a51ca30583b59b71b6547b88d954e029598043551"}, - {file = "lxml-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1cce2eaad7e38b985b0f91f18468dda0d6b91862d32bec945b0e46e2ffe7222e"}, - {file = "lxml-5.2.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60a3983d32f722a8422c01e4dc4badc7a307ca55c59e2485d0e14244a52c482f"}, - {file = "lxml-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60847dfbdfddf08a56c4eefe48234e8c1ab756c7eda4a2a7c1042666a5516564"}, - {file = "lxml-5.2.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bbe335f0d1a86391671d975a1b5e9b08bb72fba6b567c43bdc2e55ca6e6c086"}, - {file = "lxml-5.2.0-cp36-cp36m-manylinux_2_28_aarch64.whl", hash = "sha256:3ac7c8a60b8ad51fe7bca99a634dd625d66492c502fd548dc6dc769ce7d94b6a"}, - {file = "lxml-5.2.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:73e69762cf740ac3ae81137ef9d6f15f93095f50854e233d50b29e7b8a91dbc6"}, - {file = "lxml-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:281ee1ffeb0ab06204dfcd22a90e9003f0bb2dab04101ad983d0b1773bc10588"}, - {file = "lxml-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ba3a86b0d5a5c93104cb899dff291e3ae13729c389725a876d00ef9696de5425"}, - {file = "lxml-5.2.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:356f8873b1e27b81793e30144229adf70f6d3e36e5cb7b6d289da690f4398953"}, - {file = "lxml-5.2.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2a34e74ffe92c413f197ff4967fb1611d938ee0691b762d062ef0f73814f3aa4"}, - {file = "lxml-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:6f0d2b97a5a06c00c963d4542793f3e486b1ed3a957f8c19f6006ed39d104bb0"}, - {file = "lxml-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:35e39c6fd089ad6674eb52d93aa874d6027b3ae44d2381cca6e9e4c2e102c9c8"}, - {file = "lxml-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5f6e4e5a62114ae76690c4a04c5108d067442d0a41fd092e8abd25af1288c450"}, - {file = "lxml-5.2.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93eede9bcc842f891b2267c7f0984d811940d1bc18472898a1187fe560907a99"}, - {file = "lxml-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad364026c2cebacd7e01d1138bd53639822fefa8f7da90fc38cd0e6319a2699"}, - {file = "lxml-5.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f06e4460e76468d99cc36d5b9bc6fc5f43e6662af44960e13e3f4e040aacb35"}, - {file = "lxml-5.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:ca3236f31d565555139d5b00b790ed2a98ac6f0c4470c4032f8b5e5a5dba3c1a"}, - {file = "lxml-5.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:a9b67b850ab1d304cb706cf71814b0e0c3875287083d7ec55ee69504a9c48180"}, - {file = "lxml-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5261c858c390ae9a19aba96796948b6a2d56649cbd572968970dc8da2b2b2a42"}, - {file = "lxml-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e8359fb610c8c444ac473cfd82dae465f405ff807cabb98a9b9712bbd0028751"}, - {file = "lxml-5.2.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:f9e27841cddfaebc4e3ffbe5dbdff42891051acf5befc9f5323944b2c61cef16"}, - {file = "lxml-5.2.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:641a8da145aca67671205f3e89bfec9815138cf2fe06653c909eab42e486d373"}, - {file = "lxml-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:931a3a13e0f574abce8f3152b207938a54304ccf7a6fd7dff1fdb2f6691d08af"}, - {file = "lxml-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:246c93e2503c710cf02c7e9869dc0258223cbefe5e8f9ecded0ac0aa07fd2bf8"}, - {file = "lxml-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:11acfcdf5a38cf89c48662123a5d02ae0a7d99142c7ee14ad90de5c96a9b6f06"}, - {file = "lxml-5.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:200f70b5d95fc79eb9ed7f8c4888eef4e274b9bf380b829d3d52e9ed962e9231"}, - {file = "lxml-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba4d02aed47c25be6775a40d55c5774327fdedba79871b7c2485e80e45750cb2"}, - {file = "lxml-5.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e283b24c14361fe9e04026a1d06c924450415491b83089951d469509900d9f32"}, - {file = "lxml-5.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:03e3962d6ad13a862dacd5b3a3ea60b4d092a550f36465234b8639311fd60989"}, - {file = "lxml-5.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6e45fd5213e5587a610b7e7c8c5319a77591ab21ead42df46bb342e21bc1418d"}, - {file = "lxml-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:27877732946843f4b6bfc56eb40d865653eef34ad2edeed16b015d5c29c248df"}, - {file = "lxml-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4d16b44ad0dd8c948129639e34c8d301ad87ebc852568ace6fe9a5ad9ce67ee1"}, - {file = "lxml-5.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b8f842df9ba26135c5414e93214e04fe0af259bb4f96a32f756f89467f7f3b45"}, - {file = "lxml-5.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c74e77df9e36c8c91157853e6cd400f6f9ca7a803ba89981bfe3f3fc7e5651ef"}, - {file = "lxml-5.2.0-cp38-cp38-win32.whl", hash = "sha256:1459a998c10a99711ac532abe5cc24ba354e4396dafef741c7797f8830712d56"}, - {file = "lxml-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:a00f5931b7cccea775123c3c0a2513aee58afdad8728550cc970bff32280bdd2"}, - {file = "lxml-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ddda5ba8831f258ac7e6364be03cb27aa62f50c67fd94bc1c3b6247959cc0369"}, - {file = "lxml-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56835b9e9a7767202fae06310c6b67478963e535fe185bed3bf9af5b18d2b67e"}, - {file = "lxml-5.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25fef8794f0dc89f01bdd02df6a7fec4bcb2fbbe661d571e898167a83480185e"}, - {file = "lxml-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d44af078485c4da9a7ec460162392d49d996caf89516fa0b75ad0838047122"}, - {file = "lxml-5.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f354d62345acdf22aa3e171bd9723790324a66fafe61bfe3873b86724cf6daaa"}, - {file = "lxml-5.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6a7e0935f05e1cf1a3aa1d49a87505773b04f128660eac2a24a5594ea6b1baa7"}, - {file = "lxml-5.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:75a4117b43694c72a0d89f6c18a28dc57407bde4650927d4ef5fd384bdf6dcc7"}, - {file = "lxml-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:57402d6cdd8a897ce21cf8d1ff36683583c17a16322a321184766c89a1980600"}, - {file = "lxml-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:56591e477bea531e5e1854f5dfb59309d5708669bc921562a35fd9ca5182bdcd"}, - {file = "lxml-5.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7efbce96719aa275d49ad5357886845561328bf07e1d5ab998f4e3066c5ccf15"}, - {file = "lxml-5.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a3c39def0965e8fb5c8d50973e0c7b4ce429a2fa730f3f9068a7f4f9ce78410b"}, - {file = "lxml-5.2.0-cp39-cp39-win32.whl", hash = "sha256:5188f22c00381cb44283ecb28c8d85c2db4a3035774dd851876c8647cb809c27"}, - {file = "lxml-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:ed1fe80e1fcdd1205a443bddb1ad3c3135bb1cd3f36cc996a1f4aed35960fbe8"}, - {file = "lxml-5.2.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d2b339fb790fc923ae2e9345c8633e3d0064d37ea7920c027f20c8ae6f65a91f"}, - {file = "lxml-5.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06036d60fccb21e22dd167f6d0e422b9cbdf3588a7e999a33799f9cbf01e41a5"}, - {file = "lxml-5.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1611fb9de0a269c05575c024e6d8cdf2186e3fa52b364e3b03dcad82514d57"}, - {file = "lxml-5.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:05fc3720250d221792b6e0d150afc92d20cb10c9cdaa8c8f93c2a00fbdd16015"}, - {file = "lxml-5.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:11e41ffd3cd27b0ca1c76073b27bd860f96431d9b70f383990f1827ca19f2f52"}, - {file = "lxml-5.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0382e6a3eefa3f6699b14fa77c2eb32af2ada261b75120eaf4fc028a20394975"}, - {file = "lxml-5.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be5c8e776ecbcf8c1bce71a7d90e3a3680c9ceae516cac0be08b47e9fac0ca43"}, - {file = "lxml-5.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da12b4efc93d53068888cb3b58e355b31839f2428b8f13654bd25d68b201c240"}, - {file = "lxml-5.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f8033da364bacc74aca5e319509a20bb711c8a133680ca5f35020f9eaf025"}, - {file = "lxml-5.2.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:50a26f68d090594477df8572babac64575cd5c07373f7a8319c527c8e56c0f99"}, - {file = "lxml-5.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:57cbadf028727705086047994d2e50124650e63ce5a035b0aa79ab50f001989f"}, - {file = "lxml-5.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8aa11638902ac23f944f16ce45c9f04c9d5d57bb2da66822abb721f4efe5fdbb"}, - {file = "lxml-5.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7150e630b879390e02121e71ceb1807f682b88342e2ea2082e2c8716cf8bd93"}, - {file = "lxml-5.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4add722393c99da4d51c8d9f3e1ddf435b30677f2d9ba9aeaa656f23c1b7b580"}, - {file = "lxml-5.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd0f25a431cd16f70ec1c47c10b413e7ddfe1ccaaddd1a7abd181e507c012374"}, - {file = "lxml-5.2.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:883e382695f346c2ea3ad96bdbdf4ca531788fbeedb4352be3a8fcd169fc387d"}, - {file = "lxml-5.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:80cc2b55bb6e35d3cb40936b658837eb131e9f16357241cd9ba106ae1e9c5ecb"}, - {file = "lxml-5.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:59ec2948385336e9901008fdf765780fe30f03e7fdba8090aafdbe5d1b7ea0cd"}, - {file = "lxml-5.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ddbea6e58cce1a640d9d65947f1e259423fc201c9cf9761782f355f53b7f3097"}, - {file = "lxml-5.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52d6cdea438eb7282c41c5ac00bd6d47d14bebb6e8a8d2a1c168ed9e0cacfbab"}, - {file = "lxml-5.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c556bbf88a8b667c849d326dd4dd9c6290ede5a33383ffc12b0ed17777f909d"}, - {file = "lxml-5.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:947fa8bf15d1c62c6db36c6ede9389cac54f59af27010251747f05bddc227745"}, - {file = "lxml-5.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e6cb8f7a332eaa2d876b649a748a445a38522e12f2168e5e838d1505a91cdbb7"}, - {file = "lxml-5.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:16e65223f34fd3d65259b174f0f75a4bb3d9893698e5e7d01e54cd8c5eb98d85"}, - {file = "lxml-5.2.0.tar.gz", hash = "sha256:21dc490cdb33047bc7f7ad76384f3366fa8f5146b86cc04c4af45de901393b90"}, + {file = "lxml-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1f7785f4f789fdb522729ae465adcaa099e2a3441519df750ebdccc481d961a1"}, + {file = "lxml-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cc6ee342fb7fa2471bd9b6d6fdfc78925a697bf5c2bcd0a302e98b0d35bfad3"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:794f04eec78f1d0e35d9e0c36cbbb22e42d370dda1609fb03bcd7aeb458c6377"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817d420c60a5183953c783b0547d9eb43b7b344a2c46f69513d5952a78cddf3"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2213afee476546a7f37c7a9b4ad4d74b1e112a6fafffc9185d6d21f043128c81"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b070bbe8d3f0f6147689bed981d19bbb33070225373338df755a46893528104a"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e02c5175f63effbd7c5e590399c118d5db6183bbfe8e0d118bdb5c2d1b48d937"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3dc773b2861b37b41a6136e0b72a1a44689a9c4c101e0cddb6b854016acc0aa8"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:d7520db34088c96cc0e0a3ad51a4fd5b401f279ee112aa2b7f8f976d8582606d"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:bcbf4af004f98793a95355980764b3d80d47117678118a44a80b721c9913436a"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2b44bec7adf3e9305ce6cbfa47a4395667e744097faed97abb4728748ba7d47"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1c5bb205e9212d0ebddf946bc07e73fa245c864a5f90f341d11ce7b0b854475d"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2c9d147f754b1b0e723e6afb7ba1566ecb162fe4ea657f53d2139bbf894d050a"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3545039fa4779be2df51d6395e91a810f57122290864918b172d5dc7ca5bb433"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a91481dbcddf1736c98a80b122afa0f7296eeb80b72344d7f45dc9f781551f56"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2ddfe41ddc81f29a4c44c8ce239eda5ade4e7fc305fb7311759dd6229a080052"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a7baf9ffc238e4bf401299f50e971a45bfcc10a785522541a6e3179c83eabf0a"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:31e9a882013c2f6bd2f2c974241bf4ba68c85eba943648ce88936d23209a2e01"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0a15438253b34e6362b2dc41475e7f80de76320f335e70c5528b7148cac253a1"}, + {file = "lxml-5.2.1-cp310-cp310-win32.whl", hash = "sha256:6992030d43b916407c9aa52e9673612ff39a575523c5f4cf72cdef75365709a5"}, + {file = "lxml-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:da052e7962ea2d5e5ef5bc0355d55007407087392cf465b7ad84ce5f3e25fe0f"}, + {file = "lxml-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:70ac664a48aa64e5e635ae5566f5227f2ab7f66a3990d67566d9907edcbbf867"}, + {file = "lxml-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ae67b4e737cddc96c99461d2f75d218bdf7a0c3d3ad5604d1f5e7464a2f9ffe"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f18a5a84e16886898e51ab4b1d43acb3083c39b14c8caeb3589aabff0ee0b270"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6f2c8372b98208ce609c9e1d707f6918cc118fea4e2c754c9f0812c04ca116d"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:394ed3924d7a01b5bd9a0d9d946136e1c2f7b3dc337196d99e61740ed4bc6fe1"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d077bc40a1fe984e1a9931e801e42959a1e6598edc8a3223b061d30fbd26bbc"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764b521b75701f60683500d8621841bec41a65eb739b8466000c6fdbc256c240"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3a6b45da02336895da82b9d472cd274b22dc27a5cea1d4b793874eead23dd14f"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:5ea7b6766ac2dfe4bcac8b8595107665a18ef01f8c8343f00710b85096d1b53a"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:e196a4ff48310ba62e53a8e0f97ca2bca83cdd2fe2934d8b5cb0df0a841b193a"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:200e63525948e325d6a13a76ba2911f927ad399ef64f57898cf7c74e69b71095"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dae0ed02f6b075426accbf6b2863c3d0a7eacc1b41fb40f2251d931e50188dad"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:ab31a88a651039a07a3ae327d68ebdd8bc589b16938c09ef3f32a4b809dc96ef"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:df2e6f546c4df14bc81f9498bbc007fbb87669f1bb707c6138878c46b06f6510"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5dd1537e7cc06efd81371f5d1a992bd5ab156b2b4f88834ca852de4a8ea523fa"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b9ec9c9978b708d488bec36b9e4c94d88fd12ccac3e62134a9d17ddba910ea9"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8e77c69d5892cb5ba71703c4057091e31ccf534bd7f129307a4d084d90d014b8"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d5c70e04aac1eda5c829a26d1f75c6e5286c74743133d9f742cda8e53b9c2f"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c94e75445b00319c1fad60f3c98b09cd63fe1134a8a953dcd48989ef42318534"}, + {file = "lxml-5.2.1-cp311-cp311-win32.whl", hash = "sha256:4951e4f7a5680a2db62f7f4ab2f84617674d36d2d76a729b9a8be4b59b3659be"}, + {file = "lxml-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c670c0406bdc845b474b680b9a5456c561c65cf366f8db5a60154088c92d102"}, + {file = "lxml-5.2.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:abc25c3cab9ec7fcd299b9bcb3b8d4a1231877e425c650fa1c7576c5107ab851"}, + {file = "lxml-5.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6935bbf153f9a965f1e07c2649c0849d29832487c52bb4a5c5066031d8b44fd5"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d793bebb202a6000390a5390078e945bbb49855c29c7e4d56a85901326c3b5d9"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd5562927cdef7c4f5550374acbc117fd4ecc05b5007bdfa57cc5355864e0a4"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e7259016bc4345a31af861fdce942b77c99049d6c2107ca07dc2bba2435c1d9"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:530e7c04f72002d2f334d5257c8a51bf409db0316feee7c87e4385043be136af"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59689a75ba8d7ffca577aefd017d08d659d86ad4585ccc73e43edbfc7476781a"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f9737bf36262046213a28e789cc82d82c6ef19c85a0cf05e75c670a33342ac2c"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:3a74c4f27167cb95c1d4af1c0b59e88b7f3e0182138db2501c353555f7ec57f4"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:68a2610dbe138fa8c5826b3f6d98a7cfc29707b850ddcc3e21910a6fe51f6ca0"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f0a1bc63a465b6d72569a9bba9f2ef0334c4e03958e043da1920299100bc7c08"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c2d35a1d047efd68027817b32ab1586c1169e60ca02c65d428ae815b593e65d4"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:79bd05260359170f78b181b59ce871673ed01ba048deef4bf49a36ab3e72e80b"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:865bad62df277c04beed9478fe665b9ef63eb28fe026d5dedcb89b537d2e2ea6"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:44f6c7caff88d988db017b9b0e4ab04934f11e3e72d478031efc7edcac6c622f"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71e97313406ccf55d32cc98a533ee05c61e15d11b99215b237346171c179c0b0"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:057cdc6b86ab732cf361f8b4d8af87cf195a1f6dc5b0ff3de2dced242c2015e0"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f3bbbc998d42f8e561f347e798b85513ba4da324c2b3f9b7969e9c45b10f6169"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491755202eb21a5e350dae00c6d9a17247769c64dcf62d8c788b5c135e179dc4"}, + {file = "lxml-5.2.1-cp312-cp312-win32.whl", hash = "sha256:8de8f9d6caa7f25b204fc861718815d41cbcf27ee8f028c89c882a0cf4ae4134"}, + {file = "lxml-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:f2a9efc53d5b714b8df2b4b3e992accf8ce5bbdfe544d74d5c6766c9e1146a3a"}, + {file = "lxml-5.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:70a9768e1b9d79edca17890175ba915654ee1725975d69ab64813dd785a2bd5c"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c38d7b9a690b090de999835f0443d8aa93ce5f2064035dfc48f27f02b4afc3d0"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5670fb70a828663cc37552a2a85bf2ac38475572b0e9b91283dc09efb52c41d1"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:958244ad566c3ffc385f47dddde4145088a0ab893504b54b52c041987a8c1863"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2a66bf12fbd4666dd023b6f51223aed3d9f3b40fef06ce404cb75bafd3d89536"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:9123716666e25b7b71c4e1789ec829ed18663152008b58544d95b008ed9e21e9"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:0c3f67e2aeda739d1cc0b1102c9a9129f7dc83901226cc24dd72ba275ced4218"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d5792e9b3fb8d16a19f46aa8208987cfeafe082363ee2745ea8b643d9cc5b45"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:88e22fc0a6684337d25c994381ed8a1580a6f5ebebd5ad41f89f663ff4ec2885"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:21c2e6b09565ba5b45ae161b438e033a86ad1736b8c838c766146eff8ceffff9"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:afbbdb120d1e78d2ba8064a68058001b871154cc57787031b645c9142b937a62"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:627402ad8dea044dde2eccde4370560a2b750ef894c9578e1d4f8ffd54000461"}, + {file = "lxml-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:e89580a581bf478d8dcb97d9cd011d567768e8bc4095f8557b21c4d4c5fea7d0"}, + {file = "lxml-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59565f10607c244bc4c05c0c5fa0c190c990996e0c719d05deec7030c2aa8289"}, + {file = "lxml-5.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:857500f88b17a6479202ff5fe5f580fc3404922cd02ab3716197adf1ef628029"}, + {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56c22432809085b3f3ae04e6e7bdd36883d7258fcd90e53ba7b2e463efc7a6af"}, + {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a55ee573116ba208932e2d1a037cc4b10d2c1cb264ced2184d00b18ce585b2c0"}, + {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:6cf58416653c5901e12624e4013708b6e11142956e7f35e7a83f1ab02f3fe456"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:64c2baa7774bc22dd4474248ba16fe1a7f611c13ac6123408694d4cc93d66dbd"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:74b28c6334cca4dd704e8004cba1955af0b778cf449142e581e404bd211fb619"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7221d49259aa1e5a8f00d3d28b1e0b76031655ca74bb287123ef56c3db92f213"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3dbe858ee582cbb2c6294dc85f55b5f19c918c2597855e950f34b660f1a5ede6"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:04ab5415bf6c86e0518d57240a96c4d1fcfc3cb370bb2ac2a732b67f579e5a04"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:6ab833e4735a7e5533711a6ea2df26459b96f9eec36d23f74cafe03631647c41"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f443cdef978430887ed55112b491f670bba6462cea7a7742ff8f14b7abb98d75"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9e2addd2d1866fe112bc6f80117bcc6bc25191c5ed1bfbcf9f1386a884252ae8"}, + {file = "lxml-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:f51969bac61441fd31f028d7b3b45962f3ecebf691a510495e5d2cd8c8092dbd"}, + {file = "lxml-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b58fbfa1bf7367dde8a557994e3b1637294be6cf2169810375caf8571a085c"}, + {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5"}, + {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:804f74efe22b6a227306dd890eecc4f8c59ff25ca35f1f14e7482bbce96ef10b"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08802f0c56ed150cc6885ae0788a321b73505d2263ee56dad84d200cab11c07a"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f8c09ed18ecb4ebf23e02b8e7a22a05d6411911e6fabef3a36e4f371f4f2585"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3d30321949861404323c50aebeb1943461a67cd51d4200ab02babc58bd06a86"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:b560e3aa4b1d49e0e6c847d72665384db35b2f5d45f8e6a5c0072e0283430533"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:058a1308914f20784c9f4674036527e7c04f7be6fb60f5d61353545aa7fcb739"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:adfb84ca6b87e06bc6b146dc7da7623395db1e31621c4785ad0658c5028b37d7"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:417d14450f06d51f363e41cace6488519038f940676ce9664b34ebf5653433a5"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a2dfe7e2473f9b59496247aad6e23b405ddf2e12ef0765677b0081c02d6c2c0b"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bf2e2458345d9bffb0d9ec16557d8858c9c88d2d11fed53998512504cd9df49b"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:58278b29cb89f3e43ff3e0c756abbd1518f3ee6adad9e35b51fb101c1c1daaec"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:64641a6068a16201366476731301441ce93457eb8452056f570133a6ceb15fca"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:78bfa756eab503673991bdcf464917ef7845a964903d3302c5f68417ecdc948c"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:11a04306fcba10cd9637e669fd73aa274c1c09ca64af79c041aa820ea992b637"}, + {file = "lxml-5.2.1-cp38-cp38-win32.whl", hash = "sha256:66bc5eb8a323ed9894f8fa0ee6cb3e3fb2403d99aee635078fd19a8bc7a5a5da"}, + {file = "lxml-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:9676bfc686fa6a3fa10cd4ae6b76cae8be26eb5ec6811d2a325636c460da1806"}, + {file = "lxml-5.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cf22b41fdae514ee2f1691b6c3cdeae666d8b7fa9434de445f12bbeee0cf48dd"}, + {file = "lxml-5.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec42088248c596dbd61d4ae8a5b004f97a4d91a9fd286f632e42e60b706718d7"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd53553ddad4a9c2f1f022756ae64abe16da1feb497edf4d9f87f99ec7cf86bd"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feaa45c0eae424d3e90d78823f3828e7dc42a42f21ed420db98da2c4ecf0a2cb"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddc678fb4c7e30cf830a2b5a8d869538bc55b28d6c68544d09c7d0d8f17694dc"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:853e074d4931dbcba7480d4dcab23d5c56bd9607f92825ab80ee2bd916edea53"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc4691d60512798304acb9207987e7b2b7c44627ea88b9d77489bbe3e6cc3bd4"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:beb72935a941965c52990f3a32d7f07ce869fe21c6af8b34bf6a277b33a345d3"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:6588c459c5627fefa30139be4d2e28a2c2a1d0d1c265aad2ba1935a7863a4913"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:588008b8497667f1ddca7c99f2f85ce8511f8f7871b4a06ceede68ab62dff64b"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6787b643356111dfd4032b5bffe26d2f8331556ecb79e15dacb9275da02866e"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7c17b64b0a6ef4e5affae6a3724010a7a66bda48a62cfe0674dabd46642e8b54"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:27aa20d45c2e0b8cd05da6d4759649170e8dfc4f4e5ef33a34d06f2d79075d57"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d4f2cc7060dc3646632d7f15fe68e2fa98f58e35dd5666cd525f3b35d3fed7f8"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff46d772d5f6f73564979cd77a4fffe55c916a05f3cb70e7c9c0590059fb29ef"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96323338e6c14e958d775700ec8a88346014a85e5de73ac7967db0367582049b"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:52421b41ac99e9d91934e4d0d0fe7da9f02bfa7536bb4431b4c05c906c8c6919"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7a7efd5b6d3e30d81ec68ab8a88252d7c7c6f13aaa875009fe3097eb4e30b84c"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ed777c1e8c99b63037b91f9d73a6aad20fd035d77ac84afcc205225f8f41188"}, + {file = "lxml-5.2.1-cp39-cp39-win32.whl", hash = "sha256:644df54d729ef810dcd0f7732e50e5ad1bd0a135278ed8d6bcb06f33b6b6f708"}, + {file = "lxml-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:9ca66b8e90daca431b7ca1408cae085d025326570e57749695d6a01454790e95"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b0ff53900566bc6325ecde9181d89afadc59c5ffa39bddf084aaedfe3b06a11"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6037392f2d57793ab98d9e26798f44b8b4da2f2464388588f48ac52c489ea1"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9c07e7a45bb64e21df4b6aa623cb8ba214dfb47d2027d90eac197329bb5e94"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3249cc2989d9090eeac5467e50e9ec2d40704fea9ab72f36b034ea34ee65ca98"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f42038016852ae51b4088b2862126535cc4fc85802bfe30dea3500fdfaf1864e"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:533658f8fbf056b70e434dff7e7aa611bcacb33e01f75de7f821810e48d1bb66"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:622020d4521e22fb371e15f580d153134bfb68d6a429d1342a25f051ec72df1c"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7b51824aa0ee957ccd5a741c73e6851de55f40d807f08069eb4c5a26b2baa"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c6ad0fbf105f6bcc9300c00010a2ffa44ea6f555df1a2ad95c88f5656104817"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e233db59c8f76630c512ab4a4daf5a5986da5c3d5b44b8e9fc742f2a24dbd460"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a014510830df1475176466b6087fc0c08b47a36714823e58d8b8d7709132a96"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d38c8f50ecf57f0463399569aa388b232cf1a2ffb8f0a9a5412d0db57e054860"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5aea8212fb823e006b995c4dda533edcf98a893d941f173f6c9506126188860d"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff097ae562e637409b429a7ac958a20aab237a0378c42dabaa1e3abf2f896e5f"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f5d65c39f16717a47c36c756af0fb36144069c4718824b7533f803ecdf91138"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3d0c3dd24bb4605439bf91068598d00c6370684f8de4a67c2992683f6c309d6b"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e32be23d538753a8adb6c85bd539f5fd3b15cb987404327c569dfc5fd8366e85"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cc518cea79fd1e2f6c90baafa28906d4309d24f3a63e801d855e7424c5b34144"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0af35bd8ebf84888373630f73f24e86bf016642fb8576fba49d3d6b560b7cbc"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8aca2e3a72f37bfc7b14ba96d4056244001ddcc18382bd0daa087fd2e68a354"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ca1e8188b26a819387b29c3895c47a5e618708fe6f787f3b1a471de2c4a94d9"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c8ba129e6d3b0136a0f50345b2cb3db53f6bda5dd8c7f5d83fbccba97fb5dcb5"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e998e304036198b4f6914e6a1e2b6f925208a20e2042563d9734881150c6c246"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d3be9b2076112e51b323bdf6d5a7f8a798de55fb8d95fcb64bd179460cdc0704"}, + {file = "lxml-5.2.1.tar.gz", hash = "sha256:3f7765e69bbce0906a7c74d5fe46d2c7a7596147318dbc08e4a2431f3060e306"}, ] [package.dependencies] @@ -2873,44 +2919,44 @@ nicer-shell = ["ipython"] [[package]] name = "numexpr" -version = "2.9.0" +version = "2.10.0" description = "Fast numerical expression evaluator for NumPy" optional = false python-versions = ">=3.9" files = [ - {file = "numexpr-2.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c52b4ac54514f5d4d8ead66768810cd5f77aa198e6064213d9b5c7b2e1c97c35"}, - {file = "numexpr-2.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50f57bc333f285e8c46b1ce61c6e94ec9bb74e4ea0d674d1c6c6f4a286f64fe4"}, - {file = "numexpr-2.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:943ba141f3884ffafa3fa1a3ebf3cdda9e9688a67a3c91986e6eae13dc073d43"}, - {file = "numexpr-2.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee48acd6339748a65c0e32403b802ebfadd9cb0e3b602ba5889896238eafdd61"}, - {file = "numexpr-2.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:972e29b5cecc21466c5b177e38568372ab66aab1f053ae04690a49cea09e747d"}, - {file = "numexpr-2.9.0-cp310-cp310-win32.whl", hash = "sha256:520e55d75bd99c76e376b6326e35ecf44c5ce2635a5caed72799a3885fc49173"}, - {file = "numexpr-2.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:5615497c3f34b637fda9b571f7774b6a82f2367cc1364b7a4573068dd1aabcaa"}, - {file = "numexpr-2.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bffcbc55dea5a5f5255e2586da08f00929998820e6592ee717273a08ad021eb3"}, - {file = "numexpr-2.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:374dc6ca54b2af813cb15c2b34e85092dfeac1f73d51ec358dd81876bd9adcec"}, - {file = "numexpr-2.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:549afc1622296cca3478a132c6e0fb5e55a19e08d32bc0d5a415434824a9c157"}, - {file = "numexpr-2.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c618a5895e34db0a364dcdb9960084c080f93f9d377c45b1ca9c394c24b4e77"}, - {file = "numexpr-2.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:37a7dd36fd79a2b69c3fd2bc2b51ac8270bebc69cc96e6d78f1148e147fcbfa8"}, - {file = "numexpr-2.9.0-cp311-cp311-win32.whl", hash = "sha256:00dab81d49239ea5423861ad627097b44d10d802df5f883d1b00f742139c3349"}, - {file = "numexpr-2.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:0e2574cafb18373774f351cac45ed23b5b360d9ecd1dbf3c12dac6d6eefefc87"}, - {file = "numexpr-2.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9761195526a228e05eba400b8c484c94bbabfea853b9ea35ab8fa1bf415331b1"}, - {file = "numexpr-2.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0f619e91034b346ea85a4e1856ff06011dcb7dce10a60eda75e74db90120f880"}, - {file = "numexpr-2.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2749bce1c48706d58894992634a43b8458c4ba9411191471c4565fa41e9979ec"}, - {file = "numexpr-2.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c31f621a625c7be602f92b027d90f2d3d60dcbc19b106e77fb04a4362152af"}, - {file = "numexpr-2.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b937861d13de67d440d54c85a835faed7572be5a6fd10d4f3bd4e66e157f"}, - {file = "numexpr-2.9.0-cp312-cp312-win32.whl", hash = "sha256:aa6298fb46bd7ec69911b5b80927a00663d066e719b29f48eb952d559bdd8371"}, - {file = "numexpr-2.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:8efd879839572bde5a38a1aa3ac23fd4dd9b956fb969bc5e43d1c403419e1e8c"}, - {file = "numexpr-2.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b04f12a6130094a251e3a8fff40130589c1c83be6d4eb223873bea14d8c8b630"}, - {file = "numexpr-2.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:977537f2a1cc843f888fb5f0507626f956ada674e4b3847168214a3f3c7446fa"}, - {file = "numexpr-2.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eae6c0c2d5682c02e8ac9c4287c2232c2443c9148b239df22500eaa3c5d73b7"}, - {file = "numexpr-2.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fae6828042b70c2f52a132bfcb9139da704274ed11b982fbf537f91c075d2ef"}, - {file = "numexpr-2.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c77392aea53f0700d60eb270ad63174b4ff10b04f8de92861101ca2129fee51"}, - {file = "numexpr-2.9.0-cp39-cp39-win32.whl", hash = "sha256:3b03a6cf37a72f5b52f2b962d7ac7f565bea8eaba83c3c4e5fcf8fbb6a938153"}, - {file = "numexpr-2.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:d655b6eacc4e81006b662cba014e4615a9ddd96881b8b4db4ad0d7f6d38069af"}, - {file = "numexpr-2.9.0.tar.gz", hash = "sha256:f21d12f6c432ce349089eb95342babf6629aebb3fddf187a4492d3aadaadaaf0"}, + {file = "numexpr-2.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1af6dc6b3bd2e11a802337b352bf58f30df0b70be16c4f863b70a3af3a8ef95e"}, + {file = "numexpr-2.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c66dc0188358cdcc9465b6ee54fd5eef2e83ac64b1d4ba9117c41df59bf6fca"}, + {file = "numexpr-2.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83f1e7a7f7ee741b8dcd20c56c3f862a3a3ec26fa8b9fcadb7dcd819876d2f35"}, + {file = "numexpr-2.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f0b045e1831953a47cc9fabae76a6794c69cbb60921751a5cf2d555034c55bf"}, + {file = "numexpr-2.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1d8eb88b0ae3d3c609d732a17e71096779b2bf47b3a084320ffa93d9f9132786"}, + {file = "numexpr-2.10.0-cp310-cp310-win32.whl", hash = "sha256:629b66cc1b750671e7fb396506b3f9410612e5bd8bc1dd55b5a0a0041d839f95"}, + {file = "numexpr-2.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:78e0a8bc4417c3dedcbae3c473505b69080535246edc977c7dccf3ec8454a685"}, + {file = "numexpr-2.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a602692cd52ce923ce8a0a90fb1d6cf186ebe8706eed83eee0de685e634b9aa9"}, + {file = "numexpr-2.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:745b46a1fb76920a3eebfaf26e50bc94a9c13b5aee34b256ab4b2d792dbaa9ca"}, + {file = "numexpr-2.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10789450032357afaeda4ac4d06da9542d1535c13151e8d32b49ae1a488d1358"}, + {file = "numexpr-2.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4feafc65ea3044b8bf8f305b757a928e59167a310630c22b97a57dff07a56490"}, + {file = "numexpr-2.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:937d36c6d3cf15601f26f84f0f706649f976491e9e0892d16cd7c876d77fa7dc"}, + {file = "numexpr-2.10.0-cp311-cp311-win32.whl", hash = "sha256:03d0ba492e484a5a1aeb24b300c4213ed168f2c246177be5733abb4e18cbb043"}, + {file = "numexpr-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:6b5f8242c075477156d26b3a6b8e0cd0a06d4c8eb68d907bde56dd3c9c683e92"}, + {file = "numexpr-2.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b276e2ba3e87ace9a30fd49078ad5dcdc6a1674d030b1ec132599c55465c0346"}, + {file = "numexpr-2.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb5e12787101f1216f2cdabedc3417748f2e1f472442e16bbfabf0bab2336300"}, + {file = "numexpr-2.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05278bad96b5846d712eba58b44e5cec743bdb3e19ca624916c921d049fdbcf6"}, + {file = "numexpr-2.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6cdf9e64c5b3dbb61729edb505ea75ee212fa02b85c5b1d851331381ae3b0e1"}, + {file = "numexpr-2.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e3a973265591b0a875fd1151c4549e468959c7192821aac0bb86937694a08efa"}, + {file = "numexpr-2.10.0-cp312-cp312-win32.whl", hash = "sha256:416e0e9f0fc4cced67767585e44cb6b301728bdb9edbb7c534a853222ec62cac"}, + {file = "numexpr-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:748e8d4cde22d9a5603165293fb293a4de1a4623513299416c64fdab557118c2"}, + {file = "numexpr-2.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc3506c30c03b082da2cadef43747d474e5170c1f58a6dcdf882b3dc88b1e849"}, + {file = "numexpr-2.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efa63ecdc9fcaf582045639ddcf56e9bdc1f4d9a01729be528f62df4db86c9d6"}, + {file = "numexpr-2.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a64d0dd8f8e694da3f8582d73d7da8446ff375f6dd239b546010efea371ac3"}, + {file = "numexpr-2.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d47bb567e330ebe86781864219a36cbccb3a47aec893bd509f0139c6b23e8104"}, + {file = "numexpr-2.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c7517b774d309b1f0896c89bdd1ddd33c4418a92ecfbe5e1df3ac698698f6fcf"}, + {file = "numexpr-2.10.0-cp39-cp39-win32.whl", hash = "sha256:04e8620e7e676504201d4082e7b3ee2d9b561d1cb9470b47a6104e10c1e2870e"}, + {file = "numexpr-2.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:56d0d96b130f7cd4d78d0017030d6a0e9d9fc2a717ac51d4cf4860b39637e86a"}, + {file = "numexpr-2.10.0.tar.gz", hash = "sha256:c89e930752639df040539160326d8f99a84159bbea41943ab8e960591edaaef0"}, ] [package.dependencies] -numpy = ">=1.13.3" +numpy = ">=1.19.3" [[package]] name = "numpy" @@ -3566,13 +3612,13 @@ pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] @@ -4056,7 +4102,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -4331,45 +4376,45 @@ tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc ( [[package]] name = "scipy" -version = "1.12.0" +version = "1.13.0" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, - {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, - {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, - {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, - {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, - {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, - {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, - {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, - {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, - {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, - {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, - {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, - {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, - {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, - {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, - {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, - {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, - {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, - {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, - {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, - {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, - {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, - {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, - {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, - {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, + {file = "scipy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba419578ab343a4e0a77c0ef82f088238a93eef141b2b8017e46149776dfad4d"}, + {file = "scipy-1.13.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:22789b56a999265431c417d462e5b7f2b487e831ca7bef5edeb56efe4c93f86e"}, + {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f1432ba070e90d42d7fd836462c50bf98bd08bed0aa616c359eed8a04e3922"}, + {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8434f6f3fa49f631fae84afee424e2483289dfc30a47755b4b4e6b07b2633a4"}, + {file = "scipy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dcbb9ea49b0167de4167c40eeee6e167caeef11effb0670b554d10b1e693a8b9"}, + {file = "scipy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1d2f7bb14c178f8b13ebae93f67e42b0a6b0fc50eba1cd8021c9b6e08e8fb1cd"}, + {file = "scipy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fbcf8abaf5aa2dc8d6400566c1a727aed338b5fe880cde64907596a89d576fa"}, + {file = "scipy-1.13.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5e4a756355522eb60fcd61f8372ac2549073c8788f6114449b37e9e8104f15a5"}, + {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5acd8e1dbd8dbe38d0004b1497019b2dbbc3d70691e65d69615f8a7292865d7"}, + {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ff7dad5d24a8045d836671e082a490848e8639cabb3dbdacb29f943a678683d"}, + {file = "scipy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4dca18c3ffee287ddd3bc8f1dabaf45f5305c5afc9f8ab9cbfab855e70b2df5c"}, + {file = "scipy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a2f471de4d01200718b2b8927f7d76b5d9bde18047ea0fa8bd15c5ba3f26a1d6"}, + {file = "scipy-1.13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0de696f589681c2802f9090fff730c218f7c51ff49bf252b6a97ec4a5d19e8b"}, + {file = "scipy-1.13.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b2a3ff461ec4756b7e8e42e1c681077349a038f0686132d623fa404c0bee2551"}, + {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf9fe63e7a4bf01d3645b13ff2aa6dea023d38993f42aaac81a18b1bda7a82a"}, + {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e7626dfd91cdea5714f343ce1176b6c4745155d234f1033584154f60ef1ff42"}, + {file = "scipy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:109d391d720fcebf2fbe008621952b08e52907cf4c8c7efc7376822151820820"}, + {file = "scipy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8930ae3ea371d6b91c203b1032b9600d69c568e537b7988a3073dfe4d4774f21"}, + {file = "scipy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5407708195cb38d70fd2d6bb04b1b9dd5c92297d86e9f9daae1576bd9e06f602"}, + {file = "scipy-1.13.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:ac38c4c92951ac0f729c4c48c9e13eb3675d9986cc0c83943784d7390d540c78"}, + {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c74543c4fbeb67af6ce457f6a6a28e5d3739a87f62412e4a16e46f164f0ae5"}, + {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28e286bf9ac422d6beb559bc61312c348ca9b0f0dae0d7c5afde7f722d6ea13d"}, + {file = "scipy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33fde20efc380bd23a78a4d26d59fc8704e9b5fd9b08841693eb46716ba13d86"}, + {file = "scipy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:45c08bec71d3546d606989ba6e7daa6f0992918171e2a6f7fbedfa7361c2de1e"}, + {file = "scipy-1.13.0.tar.gz", hash = "sha256:58569af537ea29d3f78e5abd18398459f195546bb3be23d16677fb26616cc11e"}, ] [package.dependencies] -numpy = ">=1.22.4,<1.29.0" +numpy = ">=1.22.4,<2.3" [package.extras] -dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "semver" @@ -4708,13 +4753,13 @@ catalogue = ">=2.0.3,<2.1.0" [[package]] name = "starlette" -version = "0.36.3" +version = "0.37.2" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"}, - {file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"}, + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, ] [package.dependencies] @@ -5536,13 +5581,13 @@ wasabi = ">=0.9.1,<1.2.0" [[package]] name = "web3" -version = "6.15.1" +version = "6.16.0" description = "web3.py" optional = false python-versions = ">=3.7.2" files = [ - {file = "web3-6.15.1-py3-none-any.whl", hash = "sha256:4e4a8313aa4556ecde61c852a62405b853b667498b07da6ff05c29fe8c79096b"}, - {file = "web3-6.15.1.tar.gz", hash = "sha256:f9e7eefc1b3c3d194868a4ef9583b625c18ea3f31a48ebe143183db74898f381"}, + {file = "web3-6.16.0-py3-none-any.whl", hash = "sha256:50e96cc447823444510ee659586b264ebc7ddbfc74cccb720d042146aa404348"}, + {file = "web3-6.16.0.tar.gz", hash = "sha256:b10c93476c106acc44b8428e47c61c385b7d0885e82cdc24049d27f521833552"}, ] [package.dependencies] @@ -5784,4 +5829,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "74488c653025e5e25ec2afb79da090ce885caff558f3928b87a7854e317119a7" +content-hash = "8666bd4554eb59cdf1b226b521456aaa8e5376c872929dd3ca9aa586b4be7ca8" diff --git a/pyproject.toml b/pyproject.toml index 00407086..84fd02ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ google-api-python-client = "==2.95.0" eth-utils = "==2.2.0" eth-abi = "==4.0.0" pycryptodome = "==3.18.0" -anthropic = "==0.3.11" +anthropic = "0.21.3" langchain = "==0.0.303" scikit-learn = "==1.3.1" pytest = "==7.2.1" @@ -69,4 +69,3 @@ docstring-parser = "0.15" faiss-cpu = "1.7.4" pypdf2 = "^3.0.1" lxml = {extras = ["html-clean"], version = "^5.2.0"} -lxml-html-clean = "^0.1.0"