diff --git a/alembic/versions/d211df879a5f_add_agent_id_to_steps.py b/alembic/versions/d211df879a5f_add_agent_id_to_steps.py new file mode 100644 index 00000000..d857fca4 --- /dev/null +++ b/alembic/versions/d211df879a5f_add_agent_id_to_steps.py @@ -0,0 +1,31 @@ +"""add agent id to steps + +Revision ID: d211df879a5f +Revises: 2f4ede6ae33b +Create Date: 2025-03-06 21:42:22.289345 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "d211df879a5f" +down_revision: Union[str, None] = "2f4ede6ae33b" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("steps", sa.Column("agent_id", sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("steps", "agent_id") + # ### end Alembic commands ### diff --git a/letta/__init__.py b/letta/__init__.py index 8fb05917..5f2bb7ac 100644 --- a/letta/__init__.py +++ b/letta/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.6.37" +__version__ = "0.6.38" # import clients from letta.client.client import LocalClient, RESTClient, create_client diff --git a/letta/agent.py b/letta/agent.py index d6a62de7..023157d0 100644 --- a/letta/agent.py +++ b/letta/agent.py @@ -29,6 +29,7 @@ from letta.helpers.json_helpers import json_dumps, json_loads from letta.interface import AgentInterface from letta.llm_api.helpers import calculate_summarizer_cutoff, get_token_counts_for_messages, is_context_overflow_error from letta.llm_api.llm_api_tools import create +from letta.llm_api.llm_client import LLMClient from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages from letta.log import get_logger from letta.memory import summarize_messages @@ -356,19 +357,38 @@ class Agent(BaseAgent): for attempt in range(1, empty_response_retry_limit + 1): try: log_telemetry(self.logger, "_get_ai_reply create start") - response = create( + # New LLM client flow + llm_client = LLMClient.create( + agent_id=self.agent_state.id, llm_config=self.agent_state.llm_config, - messages=message_sequence, - user_id=self.agent_state.created_by_id, - functions=allowed_functions, - # functions_python=self.functions_python, do we need this? - function_call=function_call, - first_message=first_message, - force_tool_call=force_tool_call, - stream=stream, - stream_interface=self.interface, put_inner_thoughts_first=put_inner_thoughts_first, + actor_id=self.agent_state.created_by_id, ) + + if llm_client and not stream: + response = llm_client.send_llm_request( + messages=message_sequence, + tools=allowed_functions, + tool_call=function_call, + stream=stream, + first_message=first_message, + force_tool_call=force_tool_call, + ) + else: + # Fallback to existing flow + response = create( + llm_config=self.agent_state.llm_config, + messages=message_sequence, + user_id=self.agent_state.created_by_id, + functions=allowed_functions, + # functions_python=self.functions_python, do we need this? + function_call=function_call, + first_message=first_message, + force_tool_call=force_tool_call, + stream=stream, + stream_interface=self.interface, + put_inner_thoughts_first=put_inner_thoughts_first, + ) log_telemetry(self.logger, "_get_ai_reply create finish") # These bottom two are retryable @@ -632,7 +652,7 @@ class Agent(BaseAgent): function_args, function_response, messages, - [tool_return] if tool_return else None, + [tool_return], include_function_failed_message=True, ) return messages, False, True # force a heartbeat to allow agent to handle error @@ -659,7 +679,7 @@ class Agent(BaseAgent): "content": function_response, "tool_call_id": tool_call_id, }, - tool_returns=[tool_return] if tool_return else None, + tool_returns=[tool_return] if sandbox_run_result else None, ) ) # extend conversation with function response self.interface.function_message(f"Ran {function_name}({function_args})", msg_obj=messages[-1]) @@ -909,6 +929,7 @@ class Agent(BaseAgent): # Log step - this must happen before messages are persisted step = self.step_manager.log_step( actor=self.user, + agent_id=self.agent_state.id, provider_name=self.agent_state.llm_config.model_endpoint_type, model=self.agent_state.llm_config.model, model_endpoint=self.agent_state.llm_config.model_endpoint, @@ -1174,6 +1195,7 @@ class Agent(BaseAgent): memory_edit_timestamp=get_utc_time(), previous_message_count=self.message_manager.size(actor=self.user, agent_id=self.agent_state.id), archival_memory_size=self.agent_manager.passage_size(actor=self.user, agent_id=self.agent_state.id), + recent_passages=self.agent_manager.list_passages(actor=self.user, agent_id=self.agent_state.id, ascending=False, limit=10), ) num_tokens_external_memory_summary = count_tokens(external_memory_summary) diff --git a/letta/client/client.py b/letta/client/client.py index e26ffa0a..4405a167 100644 --- a/letta/client/client.py +++ b/letta/client/client.py @@ -4,7 +4,6 @@ import time from typing import Callable, Dict, Generator, List, Optional, Union import requests -from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall import letta.utils from letta.constants import ADMIN_PREFIX, BASE_MEMORY_TOOLS, BASE_TOOLS, DEFAULT_HUMAN, DEFAULT_PERSONA, FUNCTION_RETURN_CHAR_LIMIT @@ -29,7 +28,7 @@ from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest from letta.schemas.letta_response import LettaResponse, LettaStreamingResponse from letta.schemas.llm_config import LLMConfig from letta.schemas.memory import ArchivalMemorySummary, ChatMemory, CreateArchivalMemory, Memory, RecallMemorySummary -from letta.schemas.message import Message, MessageCreate, MessageUpdate +from letta.schemas.message import Message, MessageCreate from letta.schemas.openai.chat_completion_response import UsageStatistics from letta.schemas.organization import Organization from letta.schemas.passage import Passage @@ -640,30 +639,6 @@ class RESTClient(AbstractClient): # refresh and return agent return self.get_agent(agent_state.id) - def update_message( - self, - agent_id: str, - message_id: str, - role: Optional[MessageRole] = None, - text: Optional[str] = None, - name: Optional[str] = None, - tool_calls: Optional[List[OpenAIToolCall]] = None, - tool_call_id: Optional[str] = None, - ) -> Message: - request = MessageUpdate( - role=role, - content=text, - name=name, - tool_calls=tool_calls, - tool_call_id=tool_call_id, - ) - response = requests.patch( - f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages/{message_id}", json=request.model_dump(), headers=self.headers - ) - if response.status_code != 200: - raise ValueError(f"Failed to update message: {response.text}") - return Message(**response.json()) - def update_agent( self, agent_id: str, @@ -2436,30 +2411,6 @@ class LocalClient(AbstractClient): # TODO: get full agent state return self.server.agent_manager.get_agent_by_id(agent_state.id, actor=self.user) - def update_message( - self, - agent_id: str, - message_id: str, - role: Optional[MessageRole] = None, - text: Optional[str] = None, - name: Optional[str] = None, - tool_calls: Optional[List[OpenAIToolCall]] = None, - tool_call_id: Optional[str] = None, - ) -> Message: - message = self.server.update_agent_message( - agent_id=agent_id, - message_id=message_id, - request=MessageUpdate( - role=role, - content=text, - name=name, - tool_calls=tool_calls, - tool_call_id=tool_call_id, - ), - actor=self.user, - ) - return message - def update_agent( self, agent_id: str, diff --git a/letta/constants.py b/letta/constants.py index e06984a3..7408d05d 100644 --- a/letta/constants.py +++ b/letta/constants.py @@ -50,7 +50,7 @@ BASE_TOOLS = ["send_message", "conversation_search", "archival_memory_insert", " # Base memory tools CAN be edited, and are added by default by the server BASE_MEMORY_TOOLS = ["core_memory_append", "core_memory_replace"] # Multi agent tools -MULTI_AGENT_TOOLS = ["send_message_to_agent_and_wait_for_reply", "send_message_to_agents_matching_all_tags", "send_message_to_agent_async"] +MULTI_AGENT_TOOLS = ["send_message_to_agent_and_wait_for_reply", "send_message_to_agents_matching_tags", "send_message_to_agent_async"] # Set of all built-in Letta tools LETTA_TOOL_SET = set(BASE_TOOLS + BASE_MEMORY_TOOLS + MULTI_AGENT_TOOLS) diff --git a/letta/functions/function_sets/multi_agent.py b/letta/functions/function_sets/multi_agent.py index bd8f7a94..1f702b24 100644 --- a/letta/functions/function_sets/multi_agent.py +++ b/letta/functions/function_sets/multi_agent.py @@ -2,7 +2,7 @@ import asyncio from typing import TYPE_CHECKING, List from letta.functions.helpers import ( - _send_message_to_agents_matching_all_tags_async, + _send_message_to_agents_matching_tags_async, execute_send_message_to_agent, fire_and_forget_send_to_agent, ) @@ -70,18 +70,19 @@ def send_message_to_agent_async(self: "Agent", message: str, other_agent_id: str return "Successfully sent message" -def send_message_to_agents_matching_all_tags(self: "Agent", message: str, tags: List[str]) -> List[str]: +def send_message_to_agents_matching_tags(self: "Agent", message: str, match_all: List[str], match_some: List[str]) -> List[str]: """ - Sends a message to all agents within the same organization that match all of the specified tags. Messages are dispatched in parallel for improved performance, with retries to handle transient issues and timeouts to ensure responsiveness. This function enforces a limit of 100 agents and does not support pagination (cursor-based queries). Each agent must match all specified tags (`match_all_tags=True`) to be included. + Sends a message to all agents within the same organization that match the specified tag criteria. Agents must possess *all* of the tags in `match_all` and *at least one* of the tags in `match_some` to receive the message. Args: message (str): The content of the message to be sent to each matching agent. - tags (List[str]): A list of tags that an agent must possess to receive the message. + match_all (List[str]): A list of tags that an agent must possess to receive the message. + match_some (List[str]): A list of tags where an agent must have at least one to qualify. Returns: - List[str]: A list of responses from the agents that matched all tags. Each - response corresponds to a single agent. Agents that do not respond will not - have an entry in the returned list. + List[str]: A list of responses from the agents that matched the filtering criteria. Each + response corresponds to a single agent. Agents that do not respond will not have an entry + in the returned list. """ - return asyncio.run(_send_message_to_agents_matching_all_tags_async(self, message, tags)) + return asyncio.run(_send_message_to_agents_matching_tags_async(self, message, match_all, match_some)) diff --git a/letta/functions/helpers.py b/letta/functions/helpers.py index 03b27e40..19446e05 100644 --- a/letta/functions/helpers.py +++ b/letta/functions/helpers.py @@ -518,8 +518,16 @@ def fire_and_forget_send_to_agent( run_in_background_thread(background_task()) -async def _send_message_to_agents_matching_all_tags_async(sender_agent: "Agent", message: str, tags: List[str]) -> List[str]: - log_telemetry(sender_agent.logger, "_send_message_to_agents_matching_all_tags_async start", message=message, tags=tags) +async def _send_message_to_agents_matching_tags_async( + sender_agent: "Agent", message: str, match_all: List[str], match_some: List[str] +) -> List[str]: + log_telemetry( + sender_agent.logger, + "_send_message_to_agents_matching_tags_async start", + message=message, + match_all=match_all, + match_some=match_some, + ) server = get_letta_server() augmented_message = ( @@ -529,9 +537,22 @@ async def _send_message_to_agents_matching_all_tags_async(sender_agent: "Agent", ) # Retrieve up to 100 matching agents - log_telemetry(sender_agent.logger, "_send_message_to_agents_matching_all_tags_async listing agents start", message=message, tags=tags) - matching_agents = server.agent_manager.list_agents(actor=sender_agent.user, tags=tags, match_all_tags=True, limit=100) - log_telemetry(sender_agent.logger, "_send_message_to_agents_matching_all_tags_async listing agents finish", message=message, tags=tags) + log_telemetry( + sender_agent.logger, + "_send_message_to_agents_matching_tags_async listing agents start", + message=message, + match_all=match_all, + match_some=match_some, + ) + matching_agents = server.agent_manager.list_agents_matching_tags(actor=sender_agent.user, match_all=match_all, match_some=match_some) + + log_telemetry( + sender_agent.logger, + "_send_message_to_agents_matching_tags_async listing agents finish", + message=message, + match_all=match_all, + match_some=match_some, + ) # Create a system message messages = [MessageCreate(role=MessageRole.system, content=augmented_message, name=sender_agent.agent_state.name)] @@ -559,7 +580,13 @@ async def _send_message_to_agents_matching_all_tags_async(sender_agent: "Agent", else: final.append(r) - log_telemetry(sender_agent.logger, "_send_message_to_agents_matching_all_tags_async finish", message=message, tags=tags) + log_telemetry( + sender_agent.logger, + "_send_message_to_agents_matching_tags_async finish", + message=message, + match_all=match_all, + match_some=match_some, + ) return final diff --git a/letta/llm_api/google_ai_client.py b/letta/llm_api/google_ai_client.py new file mode 100644 index 00000000..c75deefd --- /dev/null +++ b/letta/llm_api/google_ai_client.py @@ -0,0 +1,332 @@ +import uuid +from typing import List, Optional, Tuple + +from letta.constants import NON_USER_MSG_PREFIX +from letta.helpers.datetime_helpers import get_utc_time +from letta.helpers.json_helpers import json_dumps +from letta.llm_api.helpers import make_post_request +from letta.llm_api.llm_client_base import LLMClientBase +from letta.local_llm.json_parser import clean_json_string_extra_backslash +from letta.local_llm.utils import count_tokens +from letta.schemas.message import Message as PydanticMessage +from letta.schemas.openai.chat_completion_request import Tool +from letta.schemas.openai.chat_completion_response import ChatCompletionResponse, Choice, FunctionCall, Message, ToolCall, UsageStatistics +from letta.settings import model_settings +from letta.utils import get_tool_call_id + + +class GoogleAIClient(LLMClientBase): + + def request(self, request_data: dict) -> dict: + """ + Performs underlying request to llm and returns raw response. + """ + url, headers = self.get_gemini_endpoint_and_headers(generate_content=True) + return make_post_request(url, headers, request_data) + + def build_request_data( + self, + messages: List[PydanticMessage], + tools: List[dict], + tool_call: Optional[str], + ) -> dict: + """ + Constructs a request object in the expected data format for this client. + """ + if tools: + tools = [{"type": "function", "function": f} for f in tools] + tools = self.convert_tools_to_google_ai_format( + [Tool(**t) for t in tools], + ) + contents = self.add_dummy_model_messages( + [m.to_google_ai_dict() for m in messages], + ) + + return { + "contents": contents, + "tools": tools, + "generation_config": { + "temperature": self.llm_config.temperature, + "max_output_tokens": self.llm_config.max_tokens, + }, + } + + def convert_response_to_chat_completion( + self, + response_data: dict, + input_messages: List[PydanticMessage], + ) -> ChatCompletionResponse: + """ + Converts custom response format from llm client into an OpenAI + ChatCompletionsResponse object. + + Example Input: + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": " OK. Barbie is showing in two theaters in Mountain View, CA: AMC Mountain View 16 and Regal Edwards 14." + } + ] + } + } + ], + "usageMetadata": { + "promptTokenCount": 9, + "candidatesTokenCount": 27, + "totalTokenCount": 36 + } + } + """ + try: + choices = [] + index = 0 + for candidate in response_data["candidates"]: + content = candidate["content"] + + role = content["role"] + assert role == "model", f"Unknown role in response: {role}" + + parts = content["parts"] + # TODO support parts / multimodal + # TODO support parallel tool calling natively + # TODO Alternative here is to throw away everything else except for the first part + for response_message in parts: + # Convert the actual message style to OpenAI style + if "functionCall" in response_message and response_message["functionCall"] is not None: + function_call = response_message["functionCall"] + assert isinstance(function_call, dict), function_call + function_name = function_call["name"] + assert isinstance(function_name, str), function_name + function_args = function_call["args"] + assert isinstance(function_args, dict), function_args + + # NOTE: this also involves stripping the inner monologue out of the function + if self.llm_config.put_inner_thoughts_in_kwargs: + from letta.local_llm.constants import INNER_THOUGHTS_KWARG + + assert INNER_THOUGHTS_KWARG in function_args, f"Couldn't find inner thoughts in function args:\n{function_call}" + inner_thoughts = function_args.pop(INNER_THOUGHTS_KWARG) + assert inner_thoughts is not None, f"Expected non-null inner thoughts function arg:\n{function_call}" + else: + inner_thoughts = None + + # Google AI API doesn't generate tool call IDs + openai_response_message = Message( + role="assistant", # NOTE: "model" -> "assistant" + content=inner_thoughts, + tool_calls=[ + ToolCall( + id=get_tool_call_id(), + type="function", + function=FunctionCall( + name=function_name, + arguments=clean_json_string_extra_backslash(json_dumps(function_args)), + ), + ) + ], + ) + + else: + + # Inner thoughts are the content by default + inner_thoughts = response_message["text"] + + # Google AI API doesn't generate tool call IDs + openai_response_message = Message( + role="assistant", # NOTE: "model" -> "assistant" + content=inner_thoughts, + ) + + # Google AI API uses different finish reason strings than OpenAI + # OpenAI: 'stop', 'length', 'function_call', 'content_filter', null + # see: https://platform.openai.com/docs/guides/text-generation/chat-completions-api + # Google AI API: FINISH_REASON_UNSPECIFIED, STOP, MAX_TOKENS, SAFETY, RECITATION, OTHER + # see: https://ai.google.dev/api/python/google/ai/generativelanguage/Candidate/FinishReason + finish_reason = candidate["finishReason"] + if finish_reason == "STOP": + openai_finish_reason = ( + "function_call" + if openai_response_message.tool_calls is not None and len(openai_response_message.tool_calls) > 0 + else "stop" + ) + elif finish_reason == "MAX_TOKENS": + openai_finish_reason = "length" + elif finish_reason == "SAFETY": + openai_finish_reason = "content_filter" + elif finish_reason == "RECITATION": + openai_finish_reason = "content_filter" + else: + raise ValueError(f"Unrecognized finish reason in Google AI response: {finish_reason}") + + choices.append( + Choice( + finish_reason=openai_finish_reason, + index=index, + message=openai_response_message, + ) + ) + index += 1 + + # if len(choices) > 1: + # raise UserWarning(f"Unexpected number of candidates in response (expected 1, got {len(choices)})") + + # NOTE: some of the Google AI APIs show UsageMetadata in the response, but it seems to not exist? + # "usageMetadata": { + # "promptTokenCount": 9, + # "candidatesTokenCount": 27, + # "totalTokenCount": 36 + # } + if "usageMetadata" in response_data: + usage = UsageStatistics( + prompt_tokens=response_data["usageMetadata"]["promptTokenCount"], + completion_tokens=response_data["usageMetadata"]["candidatesTokenCount"], + total_tokens=response_data["usageMetadata"]["totalTokenCount"], + ) + else: + # Count it ourselves + assert input_messages is not None, f"Didn't get UsageMetadata from the API response, so input_messages is required" + prompt_tokens = count_tokens(json_dumps(input_messages)) # NOTE: this is a very rough approximation + completion_tokens = count_tokens(json_dumps(openai_response_message.model_dump())) # NOTE: this is also approximate + total_tokens = prompt_tokens + completion_tokens + usage = UsageStatistics( + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=total_tokens, + ) + + response_id = str(uuid.uuid4()) + return ChatCompletionResponse( + id=response_id, + choices=choices, + model=self.llm_config.model, # NOTE: Google API doesn't pass back model in the response + created=get_utc_time(), + usage=usage, + ) + except KeyError as e: + raise e + + def get_gemini_endpoint_and_headers( + self, + key_in_header: bool = True, + generate_content: bool = False, + ) -> Tuple[str, dict]: + """ + Dynamically generate the model endpoint and headers. + """ + + url = f"{self.llm_config.model_endpoint}/v1beta/models" + + # Add the model + url += f"/{self.llm_config.model}" + + # Add extension for generating content if we're hitting the LM + if generate_content: + url += ":generateContent" + + # Decide if api key should be in header or not + # Two ways to pass the key: https://ai.google.dev/tutorials/setup + if key_in_header: + headers = {"Content-Type": "application/json", "x-goog-api-key": model_settings.gemini_api_key} + else: + url += f"?key={model_settings.gemini_api_key}" + headers = {"Content-Type": "application/json"} + + return url, headers + + def convert_tools_to_google_ai_format(self, tools: List[Tool]) -> List[dict]: + """ + OpenAI style: + "tools": [{ + "type": "function", + "function": { + "name": "find_movies", + "description": "find ....", + "parameters": { + "type": "object", + "properties": { + PARAM: { + "type": PARAM_TYPE, # eg "string" + "description": PARAM_DESCRIPTION, + }, + ... + }, + "required": List[str], + } + } + } + ] + + Google AI style: + "tools": [{ + "functionDeclarations": [{ + "name": "find_movies", + "description": "find movie titles currently playing in theaters based on any description, genre, title words, etc.", + "parameters": { + "type": "OBJECT", + "properties": { + "location": { + "type": "STRING", + "description": "The city and state, e.g. San Francisco, CA or a zip code e.g. 95616" + }, + "description": { + "type": "STRING", + "description": "Any kind of description including category or genre, title words, attributes, etc." + } + }, + "required": ["description"] + } + }, { + "name": "find_theaters", + ... + """ + function_list = [ + dict( + name=t.function.name, + description=t.function.description, + parameters=t.function.parameters, # TODO need to unpack + ) + for t in tools + ] + + # Correct casing + add inner thoughts if needed + for func in function_list: + func["parameters"]["type"] = "OBJECT" + for param_name, param_fields in func["parameters"]["properties"].items(): + param_fields["type"] = param_fields["type"].upper() + # Add inner thoughts + if self.llm_config.put_inner_thoughts_in_kwargs: + from letta.local_llm.constants import INNER_THOUGHTS_KWARG, INNER_THOUGHTS_KWARG_DESCRIPTION + + func["parameters"]["properties"][INNER_THOUGHTS_KWARG] = { + "type": "STRING", + "description": INNER_THOUGHTS_KWARG_DESCRIPTION, + } + func["parameters"]["required"].append(INNER_THOUGHTS_KWARG) + + return [{"functionDeclarations": function_list}] + + def add_dummy_model_messages(self, messages: List[dict]) -> List[dict]: + """Google AI API requires all function call returns are immediately followed by a 'model' role message. + + In Letta, the 'model' will often call a function (e.g. send_message) that itself yields to the user, + so there is no natural follow-up 'model' role message. + + To satisfy the Google AI API restrictions, we can add a dummy 'yield' message + with role == 'model' that is placed in-betweeen and function output + (role == 'tool') and user message (role == 'user'). + """ + dummy_yield_message = { + "role": "model", + "parts": [{"text": f"{NON_USER_MSG_PREFIX}Function call returned, waiting for user response."}], + } + messages_with_padding = [] + for i, message in enumerate(messages): + messages_with_padding.append(message) + # Check if the current message role is 'tool' and the next message role is 'user' + if message["role"] in ["tool", "function"] and (i + 1 < len(messages) and messages[i + 1]["role"] == "user"): + messages_with_padding.append(dummy_yield_message) + + return messages_with_padding diff --git a/letta/llm_api/google_vertex_client.py b/letta/llm_api/google_vertex_client.py new file mode 100644 index 00000000..1c703249 --- /dev/null +++ b/letta/llm_api/google_vertex_client.py @@ -0,0 +1,214 @@ +import uuid +from typing import List, Optional + +from google import genai +from google.genai.types import FunctionCallingConfig, FunctionCallingConfigMode, GenerateContentResponse, ToolConfig + +from letta.helpers.datetime_helpers import get_utc_time +from letta.helpers.json_helpers import json_dumps +from letta.llm_api.google_ai_client import GoogleAIClient +from letta.local_llm.json_parser import clean_json_string_extra_backslash +from letta.local_llm.utils import count_tokens +from letta.schemas.message import Message as PydanticMessage +from letta.schemas.openai.chat_completion_response import ChatCompletionResponse, Choice, FunctionCall, Message, ToolCall, UsageStatistics +from letta.settings import model_settings +from letta.utils import get_tool_call_id + + +class GoogleVertexClient(GoogleAIClient): + + def request(self, request_data: dict) -> dict: + """ + Performs underlying request to llm and returns raw response. + """ + client = genai.Client( + vertexai=True, + project=model_settings.google_cloud_project, + location=model_settings.google_cloud_location, + http_options={"api_version": "v1"}, + ) + response = client.models.generate_content( + model=self.llm_config.model, + contents=request_data["contents"], + config=request_data["config"], + ) + return response.model_dump() + + def build_request_data( + self, + messages: List[PydanticMessage], + tools: List[dict], + tool_call: Optional[str], + ) -> dict: + """ + Constructs a request object in the expected data format for this client. + """ + request_data = super().build_request_data(messages, tools, tool_call) + request_data["config"] = request_data.pop("generation_config") + request_data["config"]["tools"] = request_data.pop("tools") + + tool_config = ToolConfig( + function_calling_config=FunctionCallingConfig( + # ANY mode forces the model to predict only function calls + mode=FunctionCallingConfigMode.ANY, + ) + ) + request_data["config"]["tool_config"] = tool_config.model_dump() + + return request_data + + def convert_response_to_chat_completion( + self, + response_data: dict, + input_messages: List[PydanticMessage], + ) -> ChatCompletionResponse: + """ + Converts custom response format from llm client into an OpenAI + ChatCompletionsResponse object. + + Example: + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": " OK. Barbie is showing in two theaters in Mountain View, CA: AMC Mountain View 16 and Regal Edwards 14." + } + ] + } + } + ], + "usageMetadata": { + "promptTokenCount": 9, + "candidatesTokenCount": 27, + "totalTokenCount": 36 + } + } + """ + response = GenerateContentResponse(**response_data) + try: + choices = [] + index = 0 + for candidate in response.candidates: + content = candidate.content + + role = content.role + assert role == "model", f"Unknown role in response: {role}" + + parts = content.parts + # TODO support parts / multimodal + # TODO support parallel tool calling natively + # TODO Alternative here is to throw away everything else except for the first part + for response_message in parts: + # Convert the actual message style to OpenAI style + if response_message.function_call: + function_call = response_message.function_call + function_name = function_call.name + function_args = function_call.args + assert isinstance(function_args, dict), function_args + + # NOTE: this also involves stripping the inner monologue out of the function + if self.llm_config.put_inner_thoughts_in_kwargs: + from letta.local_llm.constants import INNER_THOUGHTS_KWARG + + assert INNER_THOUGHTS_KWARG in function_args, f"Couldn't find inner thoughts in function args:\n{function_call}" + inner_thoughts = function_args.pop(INNER_THOUGHTS_KWARG) + assert inner_thoughts is not None, f"Expected non-null inner thoughts function arg:\n{function_call}" + else: + inner_thoughts = None + + # Google AI API doesn't generate tool call IDs + openai_response_message = Message( + role="assistant", # NOTE: "model" -> "assistant" + content=inner_thoughts, + tool_calls=[ + ToolCall( + id=get_tool_call_id(), + type="function", + function=FunctionCall( + name=function_name, + arguments=clean_json_string_extra_backslash(json_dumps(function_args)), + ), + ) + ], + ) + + else: + + # Inner thoughts are the content by default + inner_thoughts = response_message.text + + # Google AI API doesn't generate tool call IDs + openai_response_message = Message( + role="assistant", # NOTE: "model" -> "assistant" + content=inner_thoughts, + ) + + # Google AI API uses different finish reason strings than OpenAI + # OpenAI: 'stop', 'length', 'function_call', 'content_filter', null + # see: https://platform.openai.com/docs/guides/text-generation/chat-completions-api + # Google AI API: FINISH_REASON_UNSPECIFIED, STOP, MAX_TOKENS, SAFETY, RECITATION, OTHER + # see: https://ai.google.dev/api/python/google/ai/generativelanguage/Candidate/FinishReason + finish_reason = candidate.finish_reason.value + if finish_reason == "STOP": + openai_finish_reason = ( + "function_call" + if openai_response_message.tool_calls is not None and len(openai_response_message.tool_calls) > 0 + else "stop" + ) + elif finish_reason == "MAX_TOKENS": + openai_finish_reason = "length" + elif finish_reason == "SAFETY": + openai_finish_reason = "content_filter" + elif finish_reason == "RECITATION": + openai_finish_reason = "content_filter" + else: + raise ValueError(f"Unrecognized finish reason in Google AI response: {finish_reason}") + + choices.append( + Choice( + finish_reason=openai_finish_reason, + index=index, + message=openai_response_message, + ) + ) + index += 1 + + # if len(choices) > 1: + # raise UserWarning(f"Unexpected number of candidates in response (expected 1, got {len(choices)})") + + # NOTE: some of the Google AI APIs show UsageMetadata in the response, but it seems to not exist? + # "usageMetadata": { + # "promptTokenCount": 9, + # "candidatesTokenCount": 27, + # "totalTokenCount": 36 + # } + if response.usage_metadata: + usage = UsageStatistics( + prompt_tokens=response.usage_metadata.prompt_token_count, + completion_tokens=response.usage_metadata.candidates_token_count, + total_tokens=response.usage_metadata.total_token_count, + ) + else: + # Count it ourselves + assert input_messages is not None, f"Didn't get UsageMetadata from the API response, so input_messages is required" + prompt_tokens = count_tokens(json_dumps(input_messages)) # NOTE: this is a very rough approximation + completion_tokens = count_tokens(json_dumps(openai_response_message.model_dump())) # NOTE: this is also approximate + total_tokens = prompt_tokens + completion_tokens + usage = UsageStatistics( + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=total_tokens, + ) + + response_id = str(uuid.uuid4()) + return ChatCompletionResponse( + id=response_id, + choices=choices, + model=self.llm_config.model, # NOTE: Google API doesn't pass back model in the response + created=get_utc_time(), + usage=usage, + ) + except KeyError as e: + raise e diff --git a/letta/llm_api/llm_client.py b/letta/llm_api/llm_client.py new file mode 100644 index 00000000..1769cb4d --- /dev/null +++ b/letta/llm_api/llm_client.py @@ -0,0 +1,48 @@ +from typing import Optional + +from letta.llm_api.llm_client_base import LLMClientBase +from letta.schemas.llm_config import LLMConfig + + +class LLMClient: + """Factory class for creating LLM clients based on the model endpoint type.""" + + @staticmethod + def create( + agent_id: str, + llm_config: LLMConfig, + put_inner_thoughts_first: bool = True, + actor_id: Optional[str] = None, + ) -> Optional[LLMClientBase]: + """ + Create an LLM client based on the model endpoint type. + + Args: + agent_id: Unique identifier for the agent + llm_config: Configuration for the LLM model + put_inner_thoughts_first: Whether to put inner thoughts first in the response + use_structured_output: Whether to use structured output + use_tool_naming: Whether to use tool naming + actor_id: Optional actor identifier + + Returns: + An instance of LLMClientBase subclass + + Raises: + ValueError: If the model endpoint type is not supported + """ + match llm_config.model_endpoint_type: + case "google_ai": + from letta.llm_api.google_ai_client import GoogleAIClient + + return GoogleAIClient( + agent_id=agent_id, llm_config=llm_config, put_inner_thoughts_first=put_inner_thoughts_first, actor_id=actor_id + ) + case "google_vertex": + from letta.llm_api.google_vertex_client import GoogleVertexClient + + return GoogleVertexClient( + agent_id=agent_id, llm_config=llm_config, put_inner_thoughts_first=put_inner_thoughts_first, actor_id=actor_id + ) + case _: + return None diff --git a/letta/llm_api/llm_client_base.py b/letta/llm_api/llm_client_base.py new file mode 100644 index 00000000..c55658c7 --- /dev/null +++ b/letta/llm_api/llm_client_base.py @@ -0,0 +1,129 @@ +from abc import abstractmethod +from typing import List, Optional, Union + +from openai import AsyncStream, Stream +from openai.types.chat.chat_completion_chunk import ChatCompletionChunk + +from letta.schemas.llm_config import LLMConfig +from letta.schemas.message import Message +from letta.schemas.openai.chat_completion_response import ChatCompletionResponse +from letta.tracing import log_event + + +class LLMClientBase: + """ + Abstract base class for LLM clients, formatting the request objects, + handling the downstream request and parsing into chat completions response format + """ + + def __init__( + self, + agent_id: str, + llm_config: LLMConfig, + put_inner_thoughts_first: Optional[bool] = True, + use_structured_output: Optional[bool] = True, + use_tool_naming: bool = True, + actor_id: Optional[str] = None, + ): + self.agent_id = agent_id + self.llm_config = llm_config + self.put_inner_thoughts_first = put_inner_thoughts_first + self.actor_id = actor_id + + def send_llm_request( + self, + messages: List[Message], + tools: Optional[List[dict]] = None, # TODO: change to Tool object + tool_call: Optional[str] = None, + stream: bool = False, + first_message: bool = False, + force_tool_call: Optional[str] = None, + ) -> Union[ChatCompletionResponse, Stream[ChatCompletionChunk]]: + """ + Issues a request to the downstream model endpoint and parses response. + If stream=True, returns a Stream[ChatCompletionChunk] that can be iterated over. + Otherwise returns a ChatCompletionResponse. + """ + request_data = self.build_request_data(messages, tools, tool_call) + log_event(name="llm_request_sent", attributes=request_data) + if stream: + return self.stream(request_data) + else: + response_data = self.request(request_data) + log_event(name="llm_response_received", attributes=response_data) + return self.convert_response_to_chat_completion(response_data, messages) + + async def send_llm_request_async( + self, + messages: List[Message], + tools: Optional[List[dict]] = None, # TODO: change to Tool object + tool_call: Optional[str] = None, + stream: bool = False, + first_message: bool = False, + force_tool_call: Optional[str] = None, + ) -> Union[ChatCompletionResponse, AsyncStream[ChatCompletionChunk]]: + """ + Issues a request to the downstream model endpoint. + If stream=True, returns an AsyncStream[ChatCompletionChunk] that can be async iterated over. + Otherwise returns a ChatCompletionResponse. + """ + request_data = self.build_request_data(messages, tools, tool_call) + log_event(name="llm_request_sent", attributes=request_data) + if stream: + return await self.stream_async(request_data) + else: + response_data = await self.request_async(request_data) + log_event(name="llm_response_received", attributes=response_data) + return self.convert_response_to_chat_completion(response_data, messages) + + @abstractmethod + def build_request_data( + self, + messages: List[Message], + tools: List[dict], + tool_call: Optional[str], + ) -> dict: + """ + Constructs a request object in the expected data format for this client. + """ + raise NotImplementedError + + @abstractmethod + def request(self, request_data: dict) -> dict: + """ + Performs underlying request to llm and returns raw response. + """ + raise NotImplementedError + + @abstractmethod + async def request_async(self, request_data: dict) -> dict: + """ + Performs underlying request to llm and returns raw response. + """ + raise NotImplementedError + + @abstractmethod + def convert_response_to_chat_completion( + self, + response_data: dict, + input_messages: List[Message], + ) -> ChatCompletionResponse: + """ + Converts custom response format from llm client into an OpenAI + ChatCompletionsResponse object. + """ + raise NotImplementedError + + @abstractmethod + def stream(self, request_data: dict) -> Stream[ChatCompletionChunk]: + """ + Performs underlying streaming request to llm and returns raw response. + """ + raise NotImplementedError(f"Streaming is not supported for {self.llm_config.model_endpoint_type}") + + @abstractmethod + async def stream_async(self, request_data: dict) -> AsyncStream[ChatCompletionChunk]: + """ + Performs underlying streaming request to llm and returns raw response. + """ + raise NotImplementedError(f"Streaming is not supported for {self.llm_config.model_endpoint_type}") diff --git a/letta/orm/step.py b/letta/orm/step.py index f13fac6e..ce7b8244 100644 --- a/letta/orm/step.py +++ b/letta/orm/step.py @@ -33,6 +33,7 @@ class Step(SqlalchemyBase): job_id: Mapped[Optional[str]] = mapped_column( ForeignKey("jobs.id", ondelete="SET NULL"), nullable=True, doc="The unique identified of the job run that triggered this step" ) + agent_id: Mapped[Optional[str]] = mapped_column(None, nullable=True, doc="The name of the model used for this step.") provider_name: Mapped[Optional[str]] = mapped_column(None, nullable=True, doc="The name of the provider used for this step.") model: Mapped[Optional[str]] = mapped_column(None, nullable=True, doc="The name of the model used for this step.") model_endpoint: Mapped[Optional[str]] = mapped_column(None, nullable=True, doc="The model endpoint url used for this step.") diff --git a/letta/schemas/block.py b/letta/schemas/block.py index 2aa518cb..3e2fbb7e 100644 --- a/letta/schemas/block.py +++ b/letta/schemas/block.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, Field, model_validator +from pydantic import Field, model_validator from typing_extensions import Self from letta.constants import CORE_MEMORY_BLOCK_CHAR_LIMIT @@ -37,7 +37,8 @@ class BaseBlock(LettaBase, validate_assignment=True): @model_validator(mode="after") def verify_char_limit(self) -> Self: - if self.value and len(self.value) > self.limit: + # self.limit can be None from + if self.limit is not None and self.value and len(self.value) > self.limit: error_msg = f"Edit failed: Exceeds {self.limit} character limit (requested {len(self.value)}) - {str(self)}." raise ValueError(error_msg) @@ -89,61 +90,16 @@ class Persona(Block): label: str = "persona" -# class CreateBlock(BaseBlock): -# """Create a block""" -# -# is_template: bool = True -# label: str = Field(..., description="Label of the block.") - - -class BlockLabelUpdate(BaseModel): - """Update the label of a block""" - - current_label: str = Field(..., description="Current label of the block.") - new_label: str = Field(..., description="New label of the block.") - - -# class CreatePersona(CreateBlock): -# """Create a persona block""" -# -# label: str = "persona" -# -# -# class CreateHuman(CreateBlock): -# """Create a human block""" -# -# label: str = "human" - - class BlockUpdate(BaseBlock): """Update a block""" - limit: Optional[int] = Field(CORE_MEMORY_BLOCK_CHAR_LIMIT, description="Character limit of the block.") + limit: Optional[int] = Field(None, description="Character limit of the block.") value: Optional[str] = Field(None, description="Value of the block.") class Config: extra = "ignore" # Ignores extra fields -class BlockLimitUpdate(BaseModel): - """Update the limit of a block""" - - label: str = Field(..., description="Label of the block.") - limit: int = Field(..., description="New limit of the block.") - - -# class UpdatePersona(BlockUpdate): -# """Update a persona block""" -# -# label: str = "persona" -# -# -# class UpdateHuman(BlockUpdate): -# """Update a human block""" -# -# label: str = "human" - - class CreateBlock(BaseBlock): """Create a block""" diff --git a/letta/schemas/letta_message.py b/letta/schemas/letta_message.py index b66c7c12..305420e2 100644 --- a/letta/schemas/letta_message.py +++ b/letta/schemas/letta_message.py @@ -236,6 +236,32 @@ LettaMessageUnion = Annotated[ ] +class UpdateSystemMessage(BaseModel): + content: Union[str, List[MessageContentUnion]] + message_type: Literal["system_message"] = "system_message" + + +class UpdateUserMessage(BaseModel): + content: Union[str, List[MessageContentUnion]] + message_type: Literal["user_message"] = "user_message" + + +class UpdateReasoningMessage(BaseModel): + reasoning: Union[str, List[MessageContentUnion]] + message_type: Literal["reasoning_message"] = "reasoning_message" + + +class UpdateAssistantMessage(BaseModel): + content: Union[str, List[MessageContentUnion]] + message_type: Literal["assistant_message"] = "assistant_message" + + +LettaMessageUpdateUnion = Annotated[ + Union[UpdateSystemMessage, UpdateUserMessage, UpdateReasoningMessage, UpdateAssistantMessage], + Field(discriminator="message_type"), +] + + def create_letta_message_union_schema(): return { "oneOf": [ diff --git a/letta/schemas/message.py b/letta/schemas/message.py index 4490f7d7..7cf66366 100644 --- a/letta/schemas/message.py +++ b/letta/schemas/message.py @@ -74,7 +74,7 @@ class MessageUpdate(BaseModel): """Request to update a message""" role: Optional[MessageRole] = Field(None, description="The role of the participant.") - content: Optional[Union[str, List[MessageContentUnion]]] = Field(..., description="The content of the message.") + content: Optional[Union[str, List[MessageContentUnion]]] = Field(None, description="The content of the message.") # NOTE: probably doesn't make sense to allow remapping user_id or agent_id (vs creating a new message) # user_id: Optional[str] = Field(None, description="The unique identifier of the user.") # agent_id: Optional[str] = Field(None, description="The unique identifier of the agent.") diff --git a/letta/schemas/step.py b/letta/schemas/step.py index f0e7f080..d25d8b68 100644 --- a/letta/schemas/step.py +++ b/letta/schemas/step.py @@ -18,6 +18,7 @@ class Step(StepBase): job_id: Optional[str] = Field( None, description="The unique identifier of the job that this step belongs to. Only included for async calls." ) + agent_id: Optional[str] = Field(None, description="The ID of the agent that performed the step.") provider_name: Optional[str] = Field(None, description="The name of the provider used for this step.") model: Optional[str] = Field(None, description="The name of the model used for this step.") model_endpoint: Optional[str] = Field(None, description="The model endpoint url used for this step.") diff --git a/letta/serialize_schemas/agent.py b/letta/serialize_schemas/agent.py index 0baea8c2..7cef7d15 100644 --- a/letta/serialize_schemas/agent.py +++ b/letta/serialize_schemas/agent.py @@ -70,4 +70,11 @@ class SerializedAgentSchema(BaseSchema): class Meta(BaseSchema.Meta): model = Agent # TODO: Serialize these as well... - exclude = BaseSchema.Meta.exclude + ("sources", "source_passages", "agent_passages") + exclude = BaseSchema.Meta.exclude + ( + "project_id", + "template_id", + "base_template_id", + "sources", + "source_passages", + "agent_passages", + ) diff --git a/letta/server/rest_api/routers/openai/chat_completions/chat_completions.py b/letta/server/rest_api/routers/openai/chat_completions/chat_completions.py index ecf10fd7..d3715062 100644 --- a/letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +++ b/letta/server/rest_api/routers/openai/chat_completions/chat_completions.py @@ -24,7 +24,7 @@ logger = get_logger(__name__) @router.post( - "/chat/completions", + "/{agent_id}/chat/completions", response_model=None, operation_id="create_chat_completions", responses={ @@ -37,6 +37,7 @@ logger = get_logger(__name__) }, ) async def create_chat_completions( + agent_id: str, completion_request: CompletionCreateParams = Body(...), server: "SyncServer" = Depends(get_letta_server), user_id: Optional[str] = Header(None, alias="user_id"), @@ -51,12 +52,6 @@ async def create_chat_completions( actor = server.user_manager.get_user_or_default(user_id=user_id) - agent_id = str(completion_request.get("user", None)) - if agent_id is None: - error_msg = "Must pass agent_id in the 'user' field" - logger.error(error_msg) - raise HTTPException(status_code=400, detail=error_msg) - letta_agent = server.load_agent(agent_id=agent_id, actor=actor) llm_config = letta_agent.agent_state.llm_config if llm_config.model_endpoint_type != "openai" or "inference.memgpt.ai" in llm_config.model_endpoint: diff --git a/letta/server/rest_api/routers/v1/agents.py b/letta/server/rest_api/routers/v1/agents.py index e859c605..5de351a2 100644 --- a/letta/server/rest_api/routers/v1/agents.py +++ b/letta/server/rest_api/routers/v1/agents.py @@ -13,13 +13,12 @@ from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG from letta.log import get_logger from letta.orm.errors import NoResultFound from letta.schemas.agent import AgentState, CreateAgent, UpdateAgent -from letta.schemas.block import Block, BlockUpdate, CreateBlock # , BlockLabelUpdate, BlockLimitUpdate +from letta.schemas.block import Block, BlockUpdate from letta.schemas.job import JobStatus, JobUpdate, LettaRequestConfig -from letta.schemas.letta_message import LettaMessageUnion +from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion from letta.schemas.letta_request import LettaRequest, LettaStreamingRequest from letta.schemas.letta_response import LettaResponse from letta.schemas.memory import ContextWindowOverview, CreateArchivalMemory, Memory -from letta.schemas.message import Message, MessageUpdate from letta.schemas.passage import Passage, PassageUpdate from letta.schemas.run import Run from letta.schemas.source import Source @@ -119,6 +118,7 @@ async def upload_agent_serialized( True, description="If set to True, existing tools can get their source code overwritten by the uploaded tool definitions. Note that Letta core tools can never be updated externally.", ), + project_id: Optional[str] = Query(None, description="The project ID to associate the uploaded agent with."), ): """ Upload a serialized agent JSON file and recreate the agent in the system. @@ -129,7 +129,11 @@ async def upload_agent_serialized( serialized_data = await file.read() agent_json = json.loads(serialized_data) new_agent = server.agent_manager.deserialize( - serialized_agent=agent_json, actor=actor, append_copy_suffix=append_copy_suffix, override_existing_tools=override_existing_tools + serialized_agent=agent_json, + actor=actor, + append_copy_suffix=append_copy_suffix, + override_existing_tools=override_existing_tools, + project_id=project_id, ) return new_agent @@ -526,20 +530,20 @@ def list_messages( ) -@router.patch("/{agent_id}/messages/{message_id}", response_model=Message, operation_id="modify_message") +@router.patch("/{agent_id}/messages/{message_id}", response_model=LettaMessageUpdateUnion, operation_id="modify_message") def modify_message( agent_id: str, message_id: str, - request: MessageUpdate = Body(...), + request: LettaMessageUpdateUnion = Body(...), server: "SyncServer" = Depends(get_letta_server), actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present ): """ Update the details of a message associated with an agent. """ - # TODO: Get rid of agent_id here, it's not really relevant + # TODO: support modifying tool calls/returns actor = server.user_manager.get_user_or_default(user_id=actor_id) - return server.message_manager.update_message_by_id(message_id=message_id, message_update=request, actor=actor) + return server.message_manager.update_message_by_letta_message(message_id=message_id, letta_message_update=request, actor=actor) @router.post( diff --git a/letta/server/rest_api/routers/v1/steps.py b/letta/server/rest_api/routers/v1/steps.py index 7c67de9c..fa31e2bd 100644 --- a/letta/server/rest_api/routers/v1/steps.py +++ b/letta/server/rest_api/routers/v1/steps.py @@ -20,6 +20,7 @@ def list_steps( start_date: Optional[str] = Query(None, description='Return steps after this ISO datetime (e.g. "2025-01-29T15:01:19-08:00")'), end_date: Optional[str] = Query(None, description='Return steps before this ISO datetime (e.g. "2025-01-29T15:01:19-08:00")'), model: Optional[str] = Query(None, description="Filter by the name of the model used for the step"), + agent_id: Optional[str] = Query(None, description="Filter by the ID of the agent that performed the step"), server: SyncServer = Depends(get_letta_server), actor_id: Optional[str] = Header(None, alias="user_id"), ): @@ -42,6 +43,7 @@ def list_steps( limit=limit, order=order, model=model, + agent_id=agent_id, ) diff --git a/letta/server/rest_api/routers/v1/voice.py b/letta/server/rest_api/routers/v1/voice.py index 0e8b08c0..7e3871b8 100644 --- a/letta/server/rest_api/routers/v1/voice.py +++ b/letta/server/rest_api/routers/v1/voice.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Optional import httpx import openai -from fastapi import APIRouter, Body, Depends, Header, HTTPException +from fastapi import APIRouter, Body, Depends, Header from fastapi.responses import StreamingResponse from openai.types.chat.completion_create_params import CompletionCreateParams @@ -22,7 +22,7 @@ logger = get_logger(__name__) @router.post( - "/chat/completions", + "/{agent_id}/chat/completions", response_model=None, operation_id="create_voice_chat_completions", responses={ @@ -35,16 +35,13 @@ logger = get_logger(__name__) }, ) async def create_voice_chat_completions( + agent_id: str, completion_request: CompletionCreateParams = Body(...), server: "SyncServer" = Depends(get_letta_server), user_id: Optional[str] = Header(None, alias="user_id"), ): actor = server.user_manager.get_user_or_default(user_id=user_id) - agent_id = str(completion_request.get("user", None)) - if agent_id is None: - raise HTTPException(status_code=400, detail="Must pass agent_id in the 'user' field") - # Also parse the user's new input input_message = UserMessage(**get_messages_from_completion_request(completion_request)[-1]) diff --git a/letta/services/agent_manager.py b/letta/services/agent_manager.py index 6b754dd3..12ef7790 100644 --- a/letta/services/agent_manager.py +++ b/letta/services/agent_manager.py @@ -358,6 +358,49 @@ class AgentManager: return [agent.to_pydantic() for agent in agents] + @enforce_types + def list_agents_matching_tags( + self, + actor: PydanticUser, + match_all: List[str], + match_some: List[str], + limit: Optional[int] = 50, + ) -> List[PydanticAgentState]: + """ + Retrieves agents in the same organization that match all specified `match_all` tags + and at least one tag from `match_some`. The query is optimized for efficiency by + leveraging indexed filtering and aggregation. + + Args: + actor (PydanticUser): The user requesting the agent list. + match_all (List[str]): Agents must have all these tags. + match_some (List[str]): Agents must have at least one of these tags. + limit (Optional[int]): Maximum number of agents to return. + + Returns: + List[PydanticAgentState: The filtered list of matching agents. + """ + with self.session_maker() as session: + query = select(AgentModel).where(AgentModel.organization_id == actor.organization_id) + + if match_all: + # Subquery to find agent IDs that contain all match_all tags + subquery = ( + select(AgentsTags.agent_id) + .where(AgentsTags.tag.in_(match_all)) + .group_by(AgentsTags.agent_id) + .having(func.count(AgentsTags.tag) == literal(len(match_all))) + ) + query = query.where(AgentModel.id.in_(subquery)) + + if match_some: + # Ensures agents match at least one tag in match_some + query = query.join(AgentsTags).where(AgentsTags.tag.in_(match_some)) + + query = query.group_by(AgentModel.id).limit(limit) + + return list(session.execute(query).scalars()) + @enforce_types def get_agent_by_id(self, agent_id: str, actor: PydanticUser) -> PydanticAgentState: """Fetch an agent by its ID.""" @@ -401,7 +444,12 @@ class AgentManager: @enforce_types def deserialize( - self, serialized_agent: dict, actor: PydanticUser, append_copy_suffix: bool = True, override_existing_tools: bool = True + self, + serialized_agent: dict, + actor: PydanticUser, + append_copy_suffix: bool = True, + override_existing_tools: bool = True, + project_id: Optional[str] = None, ) -> PydanticAgentState: tool_data_list = serialized_agent.pop("tools", []) @@ -410,7 +458,9 @@ class AgentManager: agent = schema.load(serialized_agent, session=session) if append_copy_suffix: agent.name += "_copy" - agent.create(session, actor=actor) + if project_id: + agent.project_id = project_id + agent = agent.create(session, actor=actor) pydantic_agent = agent.to_pydantic() # Need to do this separately as there's some fancy upsert logic that SqlAlchemy cannot handle @@ -548,6 +598,7 @@ class AgentManager: system_prompt=agent_state.system, in_context_memory=agent_state.memory, in_context_memory_last_edit=memory_edit_timestamp, + recent_passages=self.list_passages(actor=actor, agent_id=agent_id, ascending=False, limit=10), ) diff = united_diff(curr_system_message_openai["content"], new_system_message_str) @@ -718,7 +769,9 @@ class AgentManager: # Commit the changes agent.update(session, actor=actor) - # Add system messsage alert to agent + # Force rebuild of system prompt so that the agent is updated with passage count + # and recent passages and add system message alert to agent + self.rebuild_system_prompt(agent_id=agent_id, actor=actor, force=True) self.append_system_message( agent_id=agent_id, content=DATA_SOURCE_ATTACH_ALERT, diff --git a/letta/services/helpers/agent_manager_helper.py b/letta/services/helpers/agent_manager_helper.py index f9549d76..e1d3c91a 100644 --- a/letta/services/helpers/agent_manager_helper.py +++ b/letta/services/helpers/agent_manager_helper.py @@ -13,6 +13,7 @@ from letta.schemas.agent import AgentState, AgentType from letta.schemas.enums import MessageRole from letta.schemas.memory import Memory from letta.schemas.message import Message, MessageCreate, TextContent +from letta.schemas.passage import Passage as PydanticPassage from letta.schemas.tool_rule import ToolRule from letta.schemas.user import User from letta.system import get_initial_boot_messages, get_login_event @@ -99,7 +100,10 @@ def derive_system_message(agent_type: AgentType, system: Optional[str] = None): # TODO: This code is kind of wonky and deserves a rewrite def compile_memory_metadata_block( - memory_edit_timestamp: datetime.datetime, previous_message_count: int = 0, archival_memory_size: int = 0 + memory_edit_timestamp: datetime.datetime, + previous_message_count: int = 0, + archival_memory_size: int = 0, + recent_passages: List[PydanticPassage] = None, ) -> str: # Put the timestamp in the local timezone (mimicking get_local_time()) timestamp_str = memory_edit_timestamp.astimezone().strftime("%Y-%m-%d %I:%M:%S %p %Z%z").strip() @@ -110,6 +114,11 @@ def compile_memory_metadata_block( f"### Memory [last modified: {timestamp_str}]", f"{previous_message_count} previous messages between you and the user are stored in recall memory (use functions to access them)", f"{archival_memory_size} total memories you created are stored in archival memory (use functions to access them)", + ( + f"Most recent archival passages {len(recent_passages)} recent passages: {[passage.text for passage in recent_passages]}" + if recent_passages is not None + else "" + ), "\nCore memory shown below (limited in size, additional information stored in archival / recall memory):", ] ) @@ -146,6 +155,7 @@ def compile_system_message( template_format: Literal["f-string", "mustache", "jinja2"] = "f-string", previous_message_count: int = 0, archival_memory_size: int = 0, + recent_passages: Optional[List[PydanticPassage]] = None, ) -> str: """Prepare the final/full system message that will be fed into the LLM API @@ -170,6 +180,7 @@ def compile_system_message( memory_edit_timestamp=in_context_memory_last_edit, previous_message_count=previous_message_count, archival_memory_size=archival_memory_size, + recent_passages=recent_passages, ) full_memory_string = memory_metadata_string + "\n" + in_context_memory.compile() diff --git a/letta/services/identity_manager.py b/letta/services/identity_manager.py index 45e77d26..42efa191 100644 --- a/letta/services/identity_manager.py +++ b/letta/services/identity_manager.py @@ -78,7 +78,13 @@ class IdentityManager: if existing_identity is None: return self.create_identity(identity=identity, actor=actor) else: - identity_update = IdentityUpdate(name=identity.name, identity_type=identity.identity_type, agent_ids=identity.agent_ids) + identity_update = IdentityUpdate( + name=identity.name, + identifier_key=identity.identifier_key, + identity_type=identity.identity_type, + agent_ids=identity.agent_ids, + properties=identity.properties, + ) return self._update_identity( session=session, existing_identity=existing_identity, identity=identity_update, actor=actor, replace=True ) diff --git a/letta/services/message_manager.py b/letta/services/message_manager.py index 26f7c27b..b07cb5d8 100644 --- a/letta/services/message_manager.py +++ b/letta/services/message_manager.py @@ -1,3 +1,4 @@ +import json from typing import List, Optional from sqlalchemy import and_, or_ @@ -7,6 +8,7 @@ from letta.orm.agent import Agent as AgentModel from letta.orm.errors import NoResultFound from letta.orm.message import Message as MessageModel from letta.schemas.enums import MessageRole +from letta.schemas.letta_message import LettaMessageUpdateUnion from letta.schemas.message import Message as PydanticMessage from letta.schemas.message import MessageUpdate from letta.schemas.user import User as PydanticUser @@ -64,6 +66,44 @@ class MessageManager: """Create multiple messages.""" return [self.create_message(m, actor=actor) for m in pydantic_msgs] + @enforce_types + def update_message_by_letta_message( + self, message_id: str, letta_message_update: LettaMessageUpdateUnion, actor: PydanticUser + ) -> PydanticMessage: + """ + Updated the underlying messages table giving an update specified to the user-facing LettaMessage + """ + message = self.get_message_by_id(message_id=message_id, actor=actor) + if letta_message_update.message_type == "assistant_message": + # modify the tool call for send_message + # TODO: fix this if we add parallel tool calls + # TODO: note this only works if the AssistantMessage is generated by the standard send_message + assert ( + message.tool_calls[0].function.name == "send_message" + ), f"Expected the first tool call to be send_message, but got {message.tool_calls[0].function.name}" + original_args = json.loads(message.tool_calls[0].function.arguments) + original_args["message"] = letta_message_update.content # override the assistant message + update_tool_call = message.tool_calls[0].__deepcopy__() + update_tool_call.function.arguments = json.dumps(original_args) + + update_message = MessageUpdate(tool_calls=[update_tool_call]) + elif letta_message_update.message_type == "reasoning_message": + update_message = MessageUpdate(content=letta_message_update.reasoning) + elif letta_message_update.message_type == "user_message" or letta_message_update.message_type == "system_message": + update_message = MessageUpdate(content=letta_message_update.content) + else: + raise ValueError(f"Unsupported message type for modification: {letta_message_update.message_type}") + + message = self.update_message_by_id(message_id=message_id, message_update=update_message, actor=actor) + + # convert back to LettaMessage + for letta_msg in message.to_letta_message(use_assistant_message=True): + if letta_msg.message_type == letta_message_update.message_type: + return letta_msg + + # raise error if message type got modified + raise ValueError(f"Message type got modified: {letta_message_update.message_type}") + @enforce_types def update_message_by_id(self, message_id: str, message_update: MessageUpdate, actor: PydanticUser) -> PydanticMessage: """ diff --git a/letta/services/step_manager.py b/letta/services/step_manager.py index dbaf9f90..fc5ed3cf 100644 --- a/letta/services/step_manager.py +++ b/letta/services/step_manager.py @@ -33,10 +33,15 @@ class StepManager: limit: Optional[int] = 50, order: Optional[str] = None, model: Optional[str] = None, + agent_id: Optional[str] = None, ) -> List[PydanticStep]: """List all jobs with optional pagination and status filter.""" with self.session_maker() as session: - filter_kwargs = {"organization_id": actor.organization_id, "model": model} + filter_kwargs = {"organization_id": actor.organization_id} + if model: + filter_kwargs["model"] = model + if agent_id: + filter_kwargs["agent_id"] = agent_id steps = StepModel.list( db_session=session, @@ -54,6 +59,7 @@ class StepManager: def log_step( self, actor: PydanticUser, + agent_id: str, provider_name: str, model: str, model_endpoint: Optional[str], @@ -65,6 +71,7 @@ class StepManager: step_data = { "origin": None, "organization_id": actor.organization_id, + "agent_id": agent_id, "provider_id": provider_id, "provider_name": provider_name, "model": model, diff --git a/poetry.lock b/poetry.lock index db0f359e..b84674d8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiohappyeyeballs" -version = "2.4.8" +version = "2.5.0" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" files = [ - {file = "aiohappyeyeballs-2.4.8-py3-none-any.whl", hash = "sha256:6cac4f5dd6e34a9644e69cf9021ef679e4394f54e58a183056d12009e42ea9e3"}, - {file = "aiohappyeyeballs-2.4.8.tar.gz", hash = "sha256:19728772cb12263077982d2f55453babd8bec6a052a926cd5c0c42796da8bf62"}, + {file = "aiohappyeyeballs-2.5.0-py3-none-any.whl", hash = "sha256:0850b580748c7071db98bffff6d4c94028d0d3035acc20fd721a0ce7e8cac35d"}, + {file = "aiohappyeyeballs-2.5.0.tar.gz", hash = "sha256:18fde6204a76deeabc97c48bdd01d5801cfda5d6b9c8bbeb1aaaee9d648ca191"}, ] [[package]] @@ -217,13 +217,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.5.3" +version = "3.6.0" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61"}, - {file = "argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392"}, + {file = "argcomplete-3.6.0-py3-none-any.whl", hash = "sha256:4e3e4e10beb20e06444dbac0ac8dda650cb6349caeefe980208d3c548708bedd"}, + {file = "argcomplete-3.6.0.tar.gz", hash = "sha256:2e4e42ec0ba2fff54b0d244d0b1623e86057673e57bafe72dda59c64bd5dee8b"}, ] [package.extras] @@ -447,17 +447,17 @@ files = [ [[package]] name = "boto3" -version = "1.37.6" +version = "1.37.10" description = "The AWS SDK for Python" optional = true python-versions = ">=3.8" files = [ - {file = "boto3-1.37.6-py3-none-any.whl", hash = "sha256:4c661389e68437a3fbc1f63decea24b88f7175e022c68622848d47fdf6e0144f"}, - {file = "boto3-1.37.6.tar.gz", hash = "sha256:e2f4a1edb7e6dbd541c2962117e1c6fea8d5a42788c441a958700a43a3ca7c47"}, + {file = "boto3-1.37.10-py3-none-any.whl", hash = "sha256:fc649fb4c9521f60660fd562d6bf2034753832968b0c93ec60ad30634afb1b0f"}, + {file = "boto3-1.37.10.tar.gz", hash = "sha256:0c6eb8191b1ea4c7a139e56425399405d46e86c7e814ef497176b9af1f7ca056"}, ] [package.dependencies] -botocore = ">=1.37.6,<1.38.0" +botocore = ">=1.37.10,<1.38.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.11.0,<0.12.0" @@ -466,13 +466,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.37.6" +version = "1.37.10" description = "Low-level, data-driven core of boto 3." optional = true python-versions = ">=3.8" files = [ - {file = "botocore-1.37.6-py3-none-any.whl", hash = "sha256:cd282fe9c8adbb55a08c7290982a98ac6cc4507fa1c493f48bc43fd6c8376a57"}, - {file = "botocore-1.37.6.tar.gz", hash = "sha256:2cb121a403cbec047d76e2401a402a6b2efd3309169037fbac588e8f7125aec4"}, + {file = "botocore-1.37.10-py3-none-any.whl", hash = "sha256:7515c8dfaaf5ba02604db9cf73c172615afee976136f31d8aec628629f24029f"}, + {file = "botocore-1.37.10.tar.gz", hash = "sha256:ab311982a9872eeb4e71906d3e3fcd2ba331a869d0fed16836ade7ce2e58bcea"}, ] [package.dependencies] @@ -500,10 +500,6 @@ files = [ {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, @@ -516,14 +512,8 @@ files = [ {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, @@ -534,24 +524,8 @@ files = [ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, - {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, - {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, @@ -561,10 +535,6 @@ files = [ {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, @@ -576,10 +546,6 @@ files = [ {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, @@ -592,10 +558,6 @@ files = [ {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, @@ -608,10 +570,6 @@ files = [ {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, @@ -874,13 +832,13 @@ test = ["pytest"] [[package]] name = "composio-core" -version = "0.7.4" +version = "0.7.7" description = "Core package to act as a bridge between composio platform and other services." optional = false python-versions = "<4,>=3.9" files = [ - {file = "composio_core-0.7.4-py3-none-any.whl", hash = "sha256:fcd0e50b2aff5b932491d532cc63d28d3b1018aeb633ae8ea96002ba75b307e7"}, - {file = "composio_core-0.7.4.tar.gz", hash = "sha256:e09f80a9dfcbd187d73174bd5fb83e25a5935347149e63d62294f0646b931bc2"}, + {file = "composio_core-0.7.7-py3-none-any.whl", hash = "sha256:2ee4824ea916509fb374ca11243bc9379f2fac01fa17fdfa23255e8a06f65ef8"}, + {file = "composio_core-0.7.7.tar.gz", hash = "sha256:386f14c906c9dd121c7af65cb12197e20c16633278627be06f89c821d17eeecb"}, ] [package.dependencies] @@ -911,13 +869,13 @@ tools = ["diskcache", "flake8", "networkx", "pathspec", "pygments", "ruff", "tra [[package]] name = "composio-langchain" -version = "0.7.4" +version = "0.7.7" description = "Use Composio to get an array of tools with your LangChain agent." optional = false python-versions = "<4,>=3.9" files = [ - {file = "composio_langchain-0.7.4-py3-none-any.whl", hash = "sha256:21a730214b33e63714991a0c4e14d58218fd164e3bee1e8a0ff974f5859d6e41"}, - {file = "composio_langchain-0.7.4.tar.gz", hash = "sha256:801ec3ae8c73bca75087d2524cdd24dde7a8cc7729b251bee8ca021b08f1b323"}, + {file = "composio_langchain-0.7.7-py3-none-any.whl", hash = "sha256:514ce3ccdb3dbea5ce24df55a967fa7297384235195eb1976d47e39bfe745252"}, + {file = "composio_langchain-0.7.7.tar.gz", hash = "sha256:9d520c523222c068a2601f5396acee9bcb3e25649069eea229c65adf9c26147b"}, ] [package.dependencies] @@ -1357,13 +1315,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "faker" -version = "36.1.1" +version = "36.2.3" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.9" files = [ - {file = "Faker-36.1.1-py3-none-any.whl", hash = "sha256:ad1f1be7fd692ec0256517404a9d7f007ab36ac5d4674082fa72404049725eaa"}, - {file = "faker-36.1.1.tar.gz", hash = "sha256:7cb2bbd4c8f040e4a340ae4019e9a48b6cf1db6a71bda4e5a61d8d13b7bef28d"}, + {file = "Faker-36.2.3-py3-none-any.whl", hash = "sha256:7ca995d65ec08d013f3c1230da7f628cb2c169a77e89cd265d7a59f443f0febd"}, + {file = "faker-36.2.3.tar.gz", hash = "sha256:1ed2d7a9c3a5657fc11a4298e8cf19f71d83740560d4ed0895b30399d482d538"}, ] [package.dependencies] @@ -1806,16 +1764,17 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "google-genai" -version = "1.4.0" +version = "1.5.0" description = "GenAI Python SDK" optional = true python-versions = ">=3.9" files = [ - {file = "google_genai-1.4.0-py3-none-any.whl", hash = "sha256:e2d2943a2ebb17fd442d539f7719975af3de07db41e2c72a04b24be0df3dadd9"}, - {file = "google_genai-1.4.0.tar.gz", hash = "sha256:808eb5b73fc81d8da92b734b5ca24fc084ebf714a4c42cc42d7dcfa47b718a18"}, + {file = "google_genai-1.5.0-py3-none-any.whl", hash = "sha256:0ad433836a402957a967ccd57cbab7768325d28966a8556771974ae1c018be59"}, + {file = "google_genai-1.5.0.tar.gz", hash = "sha256:83fcfc4956ad32ecea1fda37d8f3f7cbadbdeebd2310f2a55bc7564a2f1d459f"}, ] [package.dependencies] +anyio = ">=4.8.0,<5.0.0dev" google-auth = ">=2.14.1,<3.0.0dev" httpx = ">=0.28.1,<1.0.0dev" pydantic = ">=2.0.0,<3.0.0dev" @@ -1825,13 +1784,13 @@ websockets = ">=13.0,<15.0dev" [[package]] name = "googleapis-common-protos" -version = "1.69.0" +version = "1.69.1" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis_common_protos-1.69.0-py2.py3-none-any.whl", hash = "sha256:17835fdc4fa8da1d61cfe2d4d5d57becf7c61d4112f8d81c67eaa9d7ce43042d"}, - {file = "googleapis_common_protos-1.69.0.tar.gz", hash = "sha256:5a46d58af72846f59009b9c4710425b9af2139555c71837081706b213b298187"}, + {file = "googleapis_common_protos-1.69.1-py2.py3-none-any.whl", hash = "sha256:4077f27a6900d5946ee5a369fab9c8ded4c0ef1c6e880458ea2f70c14f7b70d5"}, + {file = "googleapis_common_protos-1.69.1.tar.gz", hash = "sha256:e20d2d8dda87da6fe7340afbbdf4f0bcb4c8fae7e6cadf55926c31f946b0b9b1"}, ] [package.dependencies] @@ -1928,137 +1887,129 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.70.0" +version = "1.71.0" description = "HTTP/2-based RPC framework" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"}, - {file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"}, - {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:374d014f29f9dfdb40510b041792e0e2828a1389281eb590df066e1cc2b404e5"}, - {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2af68a6f5c8f78d56c145161544ad0febbd7479524a59c16b3e25053f39c87f"}, - {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7df14b2dcd1102a2ec32f621cc9fab6695effef516efbc6b063ad749867295"}, - {file = "grpcio-1.70.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c78b339869f4dbf89881e0b6fbf376313e4f845a42840a7bdf42ee6caed4b11f"}, - {file = "grpcio-1.70.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58ad9ba575b39edef71f4798fdb5c7b6d02ad36d47949cd381d4392a5c9cbcd3"}, - {file = "grpcio-1.70.0-cp310-cp310-win32.whl", hash = "sha256:2b0d02e4b25a5c1f9b6c7745d4fa06efc9fd6a611af0fb38d3ba956786b95199"}, - {file = "grpcio-1.70.0-cp310-cp310-win_amd64.whl", hash = "sha256:0de706c0a5bb9d841e353f6343a9defc9fc35ec61d6eb6111802f3aa9fef29e1"}, - {file = "grpcio-1.70.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:17325b0be0c068f35770f944124e8839ea3185d6d54862800fc28cc2ffad205a"}, - {file = "grpcio-1.70.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:dbe41ad140df911e796d4463168e33ef80a24f5d21ef4d1e310553fcd2c4a386"}, - {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5ea67c72101d687d44d9c56068328da39c9ccba634cabb336075fae2eab0d04b"}, - {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb5277db254ab7586769e490b7b22f4ddab3876c490da0a1a9d7c695ccf0bf77"}, - {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7831a0fc1beeeb7759f737f5acd9fdcda520e955049512d68fda03d91186eea"}, - {file = "grpcio-1.70.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:27cc75e22c5dba1fbaf5a66c778e36ca9b8ce850bf58a9db887754593080d839"}, - {file = "grpcio-1.70.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d63764963412e22f0491d0d32833d71087288f4e24cbcddbae82476bfa1d81fd"}, - {file = "grpcio-1.70.0-cp311-cp311-win32.whl", hash = "sha256:bb491125103c800ec209d84c9b51f1c60ea456038e4734688004f377cfacc113"}, - {file = "grpcio-1.70.0-cp311-cp311-win_amd64.whl", hash = "sha256:d24035d49e026353eb042bf7b058fb831db3e06d52bee75c5f2f3ab453e71aca"}, - {file = "grpcio-1.70.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:ef4c14508299b1406c32bdbb9fb7b47612ab979b04cf2b27686ea31882387cff"}, - {file = "grpcio-1.70.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:aa47688a65643afd8b166928a1da6247d3f46a2784d301e48ca1cc394d2ffb40"}, - {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:880bfb43b1bb8905701b926274eafce5c70a105bc6b99e25f62e98ad59cb278e"}, - {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e654c4b17d07eab259d392e12b149c3a134ec52b11ecdc6a515b39aceeec898"}, - {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2394e3381071045a706ee2eeb6e08962dd87e8999b90ac15c55f56fa5a8c9597"}, - {file = "grpcio-1.70.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b3c76701428d2df01964bc6479422f20e62fcbc0a37d82ebd58050b86926ef8c"}, - {file = "grpcio-1.70.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac073fe1c4cd856ebcf49e9ed6240f4f84d7a4e6ee95baa5d66ea05d3dd0df7f"}, - {file = "grpcio-1.70.0-cp312-cp312-win32.whl", hash = "sha256:cd24d2d9d380fbbee7a5ac86afe9787813f285e684b0271599f95a51bce33528"}, - {file = "grpcio-1.70.0-cp312-cp312-win_amd64.whl", hash = "sha256:0495c86a55a04a874c7627fd33e5beaee771917d92c0e6d9d797628ac40e7655"}, - {file = "grpcio-1.70.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa573896aeb7d7ce10b1fa425ba263e8dddd83d71530d1322fd3a16f31257b4a"}, - {file = "grpcio-1.70.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:d405b005018fd516c9ac529f4b4122342f60ec1cee181788249372524e6db429"}, - {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f32090238b720eb585248654db8e3afc87b48d26ac423c8dde8334a232ff53c9"}, - {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa089a734f24ee5f6880c83d043e4f46bf812fcea5181dcb3a572db1e79e01c"}, - {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f19375f0300b96c0117aca118d400e76fede6db6e91f3c34b7b035822e06c35f"}, - {file = "grpcio-1.70.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:7c73c42102e4a5ec76608d9b60227d917cea46dff4d11d372f64cbeb56d259d0"}, - {file = "grpcio-1.70.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0a5c78d5198a1f0aa60006cd6eb1c912b4a1520b6a3968e677dbcba215fabb40"}, - {file = "grpcio-1.70.0-cp313-cp313-win32.whl", hash = "sha256:fe9dbd916df3b60e865258a8c72ac98f3ac9e2a9542dcb72b7a34d236242a5ce"}, - {file = "grpcio-1.70.0-cp313-cp313-win_amd64.whl", hash = "sha256:4119fed8abb7ff6c32e3d2255301e59c316c22d31ab812b3fbcbaf3d0d87cc68"}, - {file = "grpcio-1.70.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:8058667a755f97407fca257c844018b80004ae8035565ebc2812cc550110718d"}, - {file = "grpcio-1.70.0-cp38-cp38-macosx_10_14_universal2.whl", hash = "sha256:879a61bf52ff8ccacbedf534665bb5478ec8e86ad483e76fe4f729aaef867cab"}, - {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:0ba0a173f4feacf90ee618fbc1a27956bfd21260cd31ced9bc707ef551ff7dc7"}, - {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558c386ecb0148f4f99b1a65160f9d4b790ed3163e8610d11db47838d452512d"}, - {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:412faabcc787bbc826f51be261ae5fa996b21263de5368a55dc2cf824dc5090e"}, - {file = "grpcio-1.70.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3b0f01f6ed9994d7a0b27eeddea43ceac1b7e6f3f9d86aeec0f0064b8cf50fdb"}, - {file = "grpcio-1.70.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7385b1cb064734005204bc8994eed7dcb801ed6c2eda283f613ad8c6c75cf873"}, - {file = "grpcio-1.70.0-cp38-cp38-win32.whl", hash = "sha256:07269ff4940f6fb6710951116a04cd70284da86d0a4368fd5a3b552744511f5a"}, - {file = "grpcio-1.70.0-cp38-cp38-win_amd64.whl", hash = "sha256:aba19419aef9b254e15011b230a180e26e0f6864c90406fdbc255f01d83bc83c"}, - {file = "grpcio-1.70.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4f1937f47c77392ccd555728f564a49128b6a197a05a5cd527b796d36f3387d0"}, - {file = "grpcio-1.70.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:0cd430b9215a15c10b0e7d78f51e8a39d6cf2ea819fd635a7214fae600b1da27"}, - {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:e27585831aa6b57b9250abaf147003e126cd3a6c6ca0c531a01996f31709bed1"}, - {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1af8e15b0f0fe0eac75195992a63df17579553b0c4af9f8362cc7cc99ccddf4"}, - {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbce24409beaee911c574a3d75d12ffb8c3e3dd1b813321b1d7a96bbcac46bf4"}, - {file = "grpcio-1.70.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ff4a8112a79464919bb21c18e956c54add43ec9a4850e3949da54f61c241a4a6"}, - {file = "grpcio-1.70.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5413549fdf0b14046c545e19cfc4eb1e37e9e1ebba0ca390a8d4e9963cab44d2"}, - {file = "grpcio-1.70.0-cp39-cp39-win32.whl", hash = "sha256:b745d2c41b27650095e81dea7091668c040457483c9bdb5d0d9de8f8eb25e59f"}, - {file = "grpcio-1.70.0-cp39-cp39-win_amd64.whl", hash = "sha256:a31d7e3b529c94e930a117b2175b2efd179d96eb3c7a21ccb0289a8ab05b645c"}, - {file = "grpcio-1.70.0.tar.gz", hash = "sha256:8d1584a68d5922330025881e63a6c1b54cc8117291d382e4fa69339b6d914c56"}, + {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"}, + {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0ab8b2864396663a5b0b0d6d79495657ae85fa37dcb6498a2669d067c65c11ea"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c30f393f9d5ff00a71bb56de4aa75b8fe91b161aeb61d39528db6b768d7eac69"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f250ff44843d9a0615e350c77f890082102a0318d66a99540f54769c8766ab73"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6d8de076528f7c43a2f576bc311799f89d795aa6c9b637377cc2b1616473804"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b91879d6da1605811ebc60d21ab6a7e4bae6c35f6b63a061d61eb818c8168f6"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f71574afdf944e6652203cd1badcda195b2a27d9c83e6d88dc1ce3cfb73b31a5"}, + {file = "grpcio-1.71.0-cp310-cp310-win32.whl", hash = "sha256:8997d6785e93308f277884ee6899ba63baafa0dfb4729748200fcc537858a509"}, + {file = "grpcio-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d6ac9481d9d0d129224f6d5934d5832c4b1cddb96b59e7eba8416868909786a"}, + {file = "grpcio-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d6aa986318c36508dc1d5001a3ff169a15b99b9f96ef5e98e13522c506b37eef"}, + {file = "grpcio-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d2c170247315f2d7e5798a22358e982ad6eeb68fa20cf7a820bb74c11f0736e7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:e6f83a583ed0a5b08c5bc7a3fe860bb3c2eac1f03f1f63e0bc2091325605d2b7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be74ddeeb92cc87190e0e376dbc8fc7736dbb6d3d454f2fa1f5be1dee26b9d7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd0dfbe4d5eb1fcfec9490ca13f82b089a309dc3678e2edabc144051270a66e"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a2242d6950dc892afdf9e951ed7ff89473aaf744b7d5727ad56bdaace363722b"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0fa05ee31a20456b13ae49ad2e5d585265f71dd19fbd9ef983c28f926d45d0a7"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3d081e859fb1ebe176de33fc3adb26c7d46b8812f906042705346b314bde32c3"}, + {file = "grpcio-1.71.0-cp311-cp311-win32.whl", hash = "sha256:d6de81c9c00c8a23047136b11794b3584cdc1460ed7cbc10eada50614baa1444"}, + {file = "grpcio-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:24e867651fc67717b6f896d5f0cac0ec863a8b5fb7d6441c2ab428f52c651c6b"}, + {file = "grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537"}, + {file = "grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79"}, + {file = "grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a"}, + {file = "grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8"}, + {file = "grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379"}, + {file = "grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637"}, + {file = "grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb"}, + {file = "grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366"}, + {file = "grpcio-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c6a0a28450c16809f94e0b5bfe52cabff63e7e4b97b44123ebf77f448534d07d"}, + {file = "grpcio-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:a371e6b6a5379d3692cc4ea1cb92754d2a47bdddeee755d3203d1f84ae08e03e"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:39983a9245d37394fd59de71e88c4b295eb510a3555e0a847d9965088cdbd033"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9182e0063112e55e74ee7584769ec5a0b4f18252c35787f48738627e23a62b97"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693bc706c031aeb848849b9d1c6b63ae6bcc64057984bb91a542332b75aa4c3d"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20e8f653abd5ec606be69540f57289274c9ca503ed38388481e98fa396ed0b41"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8700a2a57771cc43ea295296330daaddc0d93c088f0a35cc969292b6db959bf3"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d35a95f05a8a2cbe8e02be137740138b3b2ea5f80bd004444e4f9a1ffc511e32"}, + {file = "grpcio-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f9c30c464cb2ddfbc2ddf9400287701270fdc0f14be5f08a1e3939f1e749b455"}, + {file = "grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a"}, + {file = "grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.70.0)"] +protobuf = ["grpcio-tools (>=1.71.0)"] [[package]] name = "grpcio-tools" -version = "1.70.0" +version = "1.71.0" description = "Protobuf code generator for gRPC" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "grpcio_tools-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:4d456521290e25b1091975af71604facc5c7db162abdca67e12a0207b8bbacbe"}, - {file = "grpcio_tools-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d50080bca84f53f3a05452e06e6251cbb4887f5a1d1321d1989e26d6e0dc398d"}, - {file = "grpcio_tools-1.70.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:02e3bf55fb569fe21b54a32925979156e320f9249bb247094c4cbaa60c23a80d"}, - {file = "grpcio_tools-1.70.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88a3ec6fa2381f616d567f996503e12ca353777941b61030fd9733fd5772860e"}, - {file = "grpcio_tools-1.70.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6034a0579fab2aed8685fa1a558de084668b1e9b01a82a4ca7458b9bedf4654c"}, - {file = "grpcio_tools-1.70.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:701bbb1ff406a21a771f5b1df6be516c0a59236774b6836eaad7696b1d128ea8"}, - {file = "grpcio_tools-1.70.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eeb86864e1432fc1ab61e03395a2a4c04e9dd9c89db07e6fe68c7c2ac8ec24f"}, - {file = "grpcio_tools-1.70.0-cp310-cp310-win32.whl", hash = "sha256:d53c8c45e843b5836781ad6b82a607c72c2f9a3f556e23d703a0e099222421fa"}, - {file = "grpcio_tools-1.70.0-cp310-cp310-win_amd64.whl", hash = "sha256:22024caee36ab65c2489594d718921dcbb5bd18d61c5417a9ede94fd8dc8a589"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:5f5aba12d98d25c7ab2dd983939e2c21556a7d15f903b286f24d88d2c6e30c0a"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d47a6c6cfc526b290b7b53a37dd7e6932983f7a168b56aab760b4b597c47f30f"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b5a9beadd1e24772ffa2c70f07d72f73330d356b78b246e424f4f2ed6c6713f3"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb8135eef160a62505f074bf7a3d62f3b13911c3c14037c5392bf877114213b5"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ac9b3e13ace8467a586c53580ee22f9732c355583f3c344ef8c6c0666219cc"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:63f367363a4a1489a0046b19f9d561216ea0d206c40a6f1bf07a58ccfb7be480"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54ceffef59a059d2c7304554a8bbb20eedb05a3f937159ab1c332c1b28e12c9f"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-win32.whl", hash = "sha256:7a90a66a46821140a2a2b0be787dfabe42e22e9a5ba9cc70726b3e5c71a3b785"}, - {file = "grpcio_tools-1.70.0-cp311-cp311-win_amd64.whl", hash = "sha256:4ebf09733545a69c166b02caa14c34451e38855544820dab7fdde5c28e2dbffe"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:ec5d6932c3173d7618267b3b3fd77b9243949c5ec04302b7338386d4f8544e0b"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:f22852da12f53b02a3bdb29d0c32fcabab9c7c8f901389acffec8461083f110d"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:7d45067e6efd20881e98a0e1d7edd7f207b1625ad7113321becbfe0a6ebee46c"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3020c97f03b30eee3c26aa2a55fbe003f1729c6f879a378507c2c78524db7c12"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7fd472fce3b33bdf7fbc24d40da7ab10d7a088bcaf59c37433c2c57330fbcb6"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3875543d74ce1a698a11f498f83795216ce929cb29afa5fac15672c7ba1d6dd2"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a130c24d617a3a57369da784080dfa8848444d41b7ae1250abc06e72e706a8d9"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-win32.whl", hash = "sha256:8eae17c920d14e2e451dbb18f5d8148f884e10228061941b33faa8fceee86e73"}, - {file = "grpcio_tools-1.70.0-cp312-cp312-win_amd64.whl", hash = "sha256:99caa530242a0a832d8b6a6ab94b190c9b449d3e237f953911b4d56207569436"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:f024688d04e7a9429489ed695b85628075c3c6d655198ba3c6ccbd1d8b7c333b"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:1fa9a81621d7178498dedcf94eb8f276a7594327faf3dd5fd1935ce2819a2bdb"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c6da2585c0950cdb650df1ff6d85b3fe31e22f8370b9ee11f8fe641d5b4bf096"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70234b592af17050ec30cf35894790cef52aeae87639efe6db854a7fa783cc8c"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c021b040d0a9f5bb96a725c4d2b95008aad127d6bed124a7bbe854973014f5b"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:114a42e566e5b16a47e98f7910a6c0074b37e2d1faacaae13222e463d0d0d43c"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:4cae365d7e3ba297256216a9a256458b286f75c64603f017972b3ad1ee374437"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-win32.whl", hash = "sha256:ae139a8d3ddd8353f62af3af018e99ebcd2f4a237bd319cb4b6f58dd608aaa54"}, - {file = "grpcio_tools-1.70.0-cp313-cp313-win_amd64.whl", hash = "sha256:04bf30c0eb2741defe3ab6e0a6102b022d69cfd39d68fab9b954993ceca8d346"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:076f71c6d5adcf237ebca63f1ed51098293261dab9f301e3dfd180e896e5fa89"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-macosx_10_14_universal2.whl", hash = "sha256:d1fc2112e9c40167086e2e6a929b253e5281bffd070fab7cd1ae019317ffc11d"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:904f13d2d04f88178b09d8ef89549b90cbf8792b684a7c72540fc1a9887697e2"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1de6c71833d36fb8cc8ac10539681756dc2c5c67e5d4aa4d05adb91ecbdd8474"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ab788afced2d2c59bef86479967ce0b28485789a9f2cc43793bb7aa67f9528b"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:836293dcbb1e59fa52aa8aa890bd7a32a8eea7651cd614e96d86de4f3032fe73"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:740b3741d124c5f390dd50ad1c42c11788882baf3c202cd3e69adee0e3dde559"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-win32.whl", hash = "sha256:b9e4a12b862ba5e42d8028da311e8d4a2c307362659b2f4141d0f940f8c12b49"}, - {file = "grpcio_tools-1.70.0-cp38-cp38-win_amd64.whl", hash = "sha256:fd04c93af460b1456cd12f8f85502503e1db6c4adc1b7d4bd775b12c1fd94fee"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:52d7e7ef11867fe7de577076b1f2ac6bf106b2325130e3de66f8c364c96ff332"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:0f7ed0372afd9f5eb938334e84681396257015ab92e03de009aa3170e64b24d0"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:24a5b0328ffcfe0c4a9024f302545abdb8d6f24921409a5839f2879555b96fea"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9387b30f3b2f46942fb5718624d7421875a6ce458620d6e15817172d78db1e1a"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4545264e06e1cd7fb21b9447bb5126330bececb4bc626c98f793fda2fd910bf8"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79b723ce30416e8e1d7ff271f97ade79aaf30309a595d80c377105c07f5b20fd"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1c0917dce12af04529606d437def83962d51c59dcde905746134222e94a2ab1b"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-win32.whl", hash = "sha256:5cb0baa52d4d44690fac6b1040197c694776a291a90e2d3c369064b4d5bc6642"}, - {file = "grpcio_tools-1.70.0-cp39-cp39-win_amd64.whl", hash = "sha256:840ec536ab933db2ef8d5acaa6b712d0e9e8f397f62907c852ec50a3f69cdb78"}, - {file = "grpcio_tools-1.70.0.tar.gz", hash = "sha256:e578fee7c1c213c8e471750d92631d00f178a15479fb2cb3b939a07fc125ccd3"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:f4ad7f0d756546902597053d70b3af2606fbd70d7972876cd75c1e241d22ae00"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:64bdb291df61cf570b5256777ad5fe2b1db6d67bc46e55dc56a0a862722ae329"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:8dd9795e982d77a4b496f7278b943c2563d9afde2069cdee78c111a40cc4d675"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1b5860c41a36b26fec4f52998f1a451d0525a5c9a4fb06b6ea3e9211abdb925"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3059c14035e5dc03d462f261e5900b9a077fd1a36976c3865b8507474520bad4"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f360981b215b1d5aff9235b37e7e1826246e35bbac32a53e41d4e990a37b8f4c"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bfe3888c3bbe16a5aa39409bc38744a31c0c3d2daa2b0095978c56e106c85b42"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:145985c0bf12131f0a1503e65763e0f060473f7f3928ed1ff3fb0e8aad5bc8ac"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-win32.whl", hash = "sha256:82c430edd939bb863550ee0fecf067d78feff828908a1b529bbe33cc57f2419c"}, + {file = "grpcio_tools-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:83e90724e3f02415c628e4ead1d6ffe063820aaaa078d9a39176793df958cd5a"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:1f19b16b49afa5d21473f49c0966dd430c88d089cd52ac02404d8cef67134efb"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:459c8f5e00e390aecd5b89de67deb3ec7188a274bc6cb50e43cef35ab3a3f45d"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:edab7e6518de01196be37f96cb1e138c3819986bf5e2a6c9e1519b4d716b2f5a"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b93b9f6adc7491d4c10144c0643409db298e5e63c997106a804f6f0248dbaf4"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ae5f2efa9e644c10bf1021600bfc099dfbd8e02b184d2d25dc31fcd6c2bc59e"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:65aa082f4435571d65d5ce07fc444f23c3eff4f3e34abef599ef8c9e1f6f360f"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1331e726e08b7bdcbf2075fcf4b47dff07842b04845e6e220a08a4663e232d7f"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6693a7d3ba138b0e693b3d1f687cdd9db9e68976c3fa2b951c17a072fea8b583"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-win32.whl", hash = "sha256:6d11ed3ff7b6023b5c72a8654975324bb98c1092426ba5b481af406ff559df00"}, + {file = "grpcio_tools-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:072b2a5805ac97e4623b3aa8f7818275f3fb087f4aa131b0fce00471065f6eaa"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:61c0409d5bdac57a7bd0ce0ab01c1c916728fe4c8a03d77a25135ad481eb505c"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:28784f39921d061d2164a9dcda5164a69d07bf29f91f0ea50b505958292312c9"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:192808cf553cedca73f0479cc61d5684ad61f24db7a5f3c4dfe1500342425866"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:989ee9da61098230d3d4c8f8f8e27c2de796f1ff21b1c90110e636d9acd9432b"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:541a756276c8a55dec991f6c0106ae20c8c8f5ce8d0bdbfcb01e2338d1a8192b"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:870c0097700d13c403e5517cb7750ab5b4a791ce3e71791c411a38c5468b64bd"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:abd57f615e88bf93c3c6fd31f923106e3beb12f8cd2df95b0d256fa07a7a0a57"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:753270e2d06d37e6d7af8967d1d059ec635ad215882041a36294f4e2fd502b2e"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-win32.whl", hash = "sha256:0e647794bd7138b8c215e86277a9711a95cf6a03ff6f9e555d54fdf7378b9f9d"}, + {file = "grpcio_tools-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:48debc879570972d28bfe98e4970eff25bb26da3f383e0e49829b2d2cd35ad87"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:9a78d07d6c301a25ef5ede962920a522556a1dfee1ccc05795994ceb867f766c"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:580ac88141c9815557e63c9c04f5b1cdb19b4db8d0cb792b573354bde1ee8b12"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f7c678e68ece0ae908ecae1c4314a0c2c7f83e26e281738b9609860cc2c82d96"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56ecd6cc89b5e5eed1de5eb9cafce86c9c9043ee3840888cc464d16200290b53"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a041afc20ab2431d756b6295d727bd7adee813b21b06a3483f4a7a15ea15f"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a1712f12102b60c8d92779b89d0504e0d6f3a59f2b933e5622b8583f5c02992"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:41878cb7a75477e62fdd45e7e9155b3af1b7a5332844021e2511deaf99ac9e6c"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:682e958b476049ccc14c71bedf3f979bced01f6e0c04852efc5887841a32ad6b"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-win32.whl", hash = "sha256:0ccfb837152b7b858b9f26bb110b3ae8c46675d56130f6c2f03605c4f129be13"}, + {file = "grpcio_tools-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:ffff9bc5eacb34dd26b487194f7d44a3e64e752fc2cf049d798021bf25053b87"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:834959b6eceb85de5217a411aba1643b5f782798680c122202d6a06177226644"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:e3ae9556e2a1cd70e7d7b0e0459c35af71d51a7dae4cf36075068011a69f13ec"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:77fe6db1334e0ce318b2cb4e70afa94e0c173ed1a533d37aea69ad9f61ae8ea9"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57e3e2544c306b60ef2d76570bac4e977be1ad548641c9eec130c3bc47e80141"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af39e245fa56f7f5c2fe86b7d6c1b78f395c07e54d5613cbdbb3c24769a92b6e"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f987d0053351217954543b174b0bddbf51d45b3cfcf8d6de97b0a43d264d753"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8e6cdbba4dae7b37b0d25d074614be9936fb720144420f03d9f142a80be69ba2"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3adc8b229e60c77bab5a5d62b415667133bd5ced7d59b5f71d6317c9143631e"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f68334d28a267fabec6e70cb5986e9999cfbfd14db654094ddf9aedd804a293a"}, + {file = "grpcio_tools-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:1291a6136c07a86c3bb09f6c33f5cf227cc14956edd1b85cb572327a36e0aef8"}, + {file = "grpcio_tools-1.71.0.tar.gz", hash = "sha256:38dba8e0d5e0fb23a034e09644fdc6ed862be2371887eee54901999e8f6792a8"}, ] [package.dependencies] -grpcio = ">=1.70.0" +grpcio = ">=1.71.0" protobuf = ">=5.26.1,<6.0dev" setuptools = "*" @@ -2169,13 +2120,13 @@ files = [ [[package]] name = "huggingface-hub" -version = "0.29.1" +version = "0.29.3" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = true python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.29.1-py3-none-any.whl", hash = "sha256:352f69caf16566c7b6de84b54a822f6238e17ddd8ae3da4f8f2272aea5b198d5"}, - {file = "huggingface_hub-0.29.1.tar.gz", hash = "sha256:9524eae42077b8ff4fc459ceb7a514eca1c1232b775276b009709fe2a084f250"}, + {file = "huggingface_hub-0.29.3-py3-none-any.whl", hash = "sha256:0b25710932ac649c08cdbefa6c6ccb8e88eef82927cacdb048efb726429453aa"}, + {file = "huggingface_hub-0.29.3.tar.gz", hash = "sha256:64519a25716e0ba382ba2d3fb3ca082e7c7eb4a2fc634d200e8380006e0760e5"}, ] [package.dependencies] @@ -2214,13 +2165,13 @@ files = [ [[package]] name = "identify" -version = "2.6.8" +version = "2.6.9" description = "File identification library for Python" optional = true python-versions = ">=3.9" files = [ - {file = "identify-2.6.8-py2.py3-none-any.whl", hash = "sha256:83657f0f766a3c8d0eaea16d4ef42494b39b34629a4b3192a9d020d349b3e255"}, - {file = "identify-2.6.8.tar.gz", hash = "sha256:61491417ea2c0c5c670484fd8abbb34de34cdae1e5f39a73ee65e48e4bb663fc"}, + {file = "identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150"}, + {file = "identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf"}, ] [package.extras] @@ -2351,13 +2302,13 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio [[package]] name = "ipython" -version = "8.33.0" +version = "8.34.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.33.0-py3-none-any.whl", hash = "sha256:aa5b301dfe1eaf0167ff3238a6825f810a029c9dad9d3f1597f30bd5ff65cc44"}, - {file = "ipython-8.33.0.tar.gz", hash = "sha256:4c3e36a6dfa9e8e3702bd46f3df668624c975a22ff340e96ea7277afbd76217d"}, + {file = "ipython-8.34.0-py3-none-any.whl", hash = "sha256:0419883fa46e0baa182c5d50ebb8d6b49df1889fdb70750ad6d8cfe678eda6e3"}, + {file = "ipython-8.34.0.tar.gz", hash = "sha256:c31d658e754673ecc6514583e7dda8069e47136eb62458816b7d1e6625948b5a"}, ] [package.dependencies] @@ -2433,13 +2384,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, - {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] @@ -2450,87 +2401,87 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.8.2" +version = "0.9.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" files = [ - {file = "jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b"}, - {file = "jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c826a221851a8dc028eb6d7d6429ba03184fa3c7e83ae01cd6d3bd1d4bd17d"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d35c864c2dff13dfd79fb070fc4fc6235d7b9b359efe340e1261deb21b9fcb66"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f557c55bc2b7676e74d39d19bcb8775ca295c7a028246175d6a8b431e70835e5"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:580ccf358539153db147e40751a0b41688a5ceb275e6f3e93d91c9467f42b2e3"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af102d3372e917cffce49b521e4c32c497515119dc7bd8a75665e90a718bbf08"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cadcc978f82397d515bb2683fc0d50103acff2a180552654bb92d6045dec2c49"}, - {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba5bdf56969cad2019d4e8ffd3f879b5fdc792624129741d3d83fc832fef8c7d"}, - {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b94a33a241bee9e34b8481cdcaa3d5c2116f575e0226e421bed3f7a6ea71cff"}, - {file = "jiter-0.8.2-cp310-cp310-win32.whl", hash = "sha256:6e5337bf454abddd91bd048ce0dca5134056fc99ca0205258766db35d0a2ea43"}, - {file = "jiter-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a9220497ca0cb1fe94e3f334f65b9b5102a0b8147646118f020d8ce1de70105"}, - {file = "jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b"}, - {file = "jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc"}, - {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88"}, - {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6"}, - {file = "jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44"}, - {file = "jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855"}, - {file = "jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f"}, - {file = "jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d"}, - {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152"}, - {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29"}, - {file = "jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e"}, - {file = "jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c"}, - {file = "jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84"}, - {file = "jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1"}, - {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9"}, - {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05"}, - {file = "jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a"}, - {file = "jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865"}, - {file = "jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca"}, - {file = "jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0"}, - {file = "jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566"}, - {file = "jiter-0.8.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9e1fa156ee9454642adb7e7234a383884452532bc9d53d5af2d18d98ada1d79c"}, - {file = "jiter-0.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cf5dfa9956d96ff2efb0f8e9c7d055904012c952539a774305aaaf3abdf3d6c"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e52bf98c7e727dd44f7c4acb980cb988448faeafed8433c867888268899b298b"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a2ecaa3c23e7a7cf86d00eda3390c232f4d533cd9ddea4b04f5d0644faf642c5"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08d4c92bf480e19fc3f2717c9ce2aa31dceaa9163839a311424b6862252c943e"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d9a1eded738299ba8e106c6779ce5c3893cffa0e32e4485d680588adae6db8"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20be8b7f606df096e08b0b1b4a3c6f0515e8dac296881fe7461dfa0fb5ec817"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d33f94615fcaf872f7fd8cd98ac3b429e435c77619777e8a449d9d27e01134d1"}, - {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:317b25e98a35ffec5c67efe56a4e9970852632c810d35b34ecdd70cc0e47b3b6"}, - {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc9043259ee430ecd71d178fccabd8c332a3bf1e81e50cae43cc2b28d19e4cb7"}, - {file = "jiter-0.8.2-cp38-cp38-win32.whl", hash = "sha256:fc5adda618205bd4678b146612ce44c3cbfdee9697951f2c0ffdef1f26d72b63"}, - {file = "jiter-0.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cd646c827b4f85ef4a78e4e58f4f5854fae0caf3db91b59f0d73731448a970c6"}, - {file = "jiter-0.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e41e75344acef3fc59ba4765df29f107f309ca9e8eace5baacabd9217e52a5ee"}, - {file = "jiter-0.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f22b16b35d5c1df9dfd58843ab2cd25e6bf15191f5a236bed177afade507bfc"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7200b8f7619d36aa51c803fd52020a2dfbea36ffec1b5e22cab11fd34d95a6d"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70bf4c43652cc294040dbb62256c83c8718370c8b93dd93d934b9a7bf6c4f53c"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9d471356dc16f84ed48768b8ee79f29514295c7295cb41e1133ec0b2b8d637d"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:859e8eb3507894093d01929e12e267f83b1d5f6221099d3ec976f0c995cb6bd9"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa58399c01db555346647a907b4ef6d4f584b123943be6ed5588c3f2359c9f4"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f2d5ed877f089862f4c7aacf3a542627c1496f972a34d0474ce85ee7d939c27"}, - {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:03c9df035d4f8d647f8c210ddc2ae0728387275340668fb30d2421e17d9a0841"}, - {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8bd2a824d08d8977bb2794ea2682f898ad3d8837932e3a74937e93d62ecbb637"}, - {file = "jiter-0.8.2-cp39-cp39-win32.whl", hash = "sha256:ca29b6371ebc40e496995c94b988a101b9fbbed48a51190a4461fcb0a68b4a36"}, - {file = "jiter-0.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c0dfbd1be3cbefc7510102370d86e35d1d53e5a93d48519688b1bf0f761160a"}, - {file = "jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d"}, + {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, + {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708"}, + {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5"}, + {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678"}, + {file = "jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4"}, + {file = "jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322"}, + {file = "jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af"}, + {file = "jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419"}, + {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043"}, + {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965"}, + {file = "jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2"}, + {file = "jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd"}, + {file = "jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11"}, + {file = "jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc"}, + {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e"}, + {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d"}, + {file = "jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06"}, + {file = "jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0"}, + {file = "jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7"}, + {file = "jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3"}, + {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5"}, + {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d"}, + {file = "jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53"}, + {file = "jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7"}, + {file = "jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001"}, + {file = "jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a"}, + {file = "jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf"}, + {file = "jiter-0.9.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4a2d16360d0642cd68236f931b85fe50288834c383492e4279d9f1792e309571"}, + {file = "jiter-0.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e84ed1c9c9ec10bbb8c37f450077cbe3c0d4e8c2b19f0a49a60ac7ace73c7452"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f3c848209ccd1bfa344a1240763975ca917de753c7875c77ec3034f4151d06c"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7825f46e50646bee937e0f849d14ef3a417910966136f59cd1eb848b8b5bb3e4"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d82a811928b26d1a6311a886b2566f68ccf2b23cf3bfed042e18686f1f22c2d7"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c058ecb51763a67f019ae423b1cbe3fa90f7ee6280c31a1baa6ccc0c0e2d06e"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9897115ad716c48f0120c1f0c4efae348ec47037319a6c63b2d7838bb53aaef4"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:351f4c90a24c4fb8c87c6a73af2944c440494ed2bea2094feecacb75c50398ae"}, + {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d45807b0f236c485e1e525e2ce3a854807dfe28ccf0d013dd4a563395e28008a"}, + {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1537a890724ba00fdba21787010ac6f24dad47f763410e9e1093277913592784"}, + {file = "jiter-0.9.0-cp38-cp38-win32.whl", hash = "sha256:e3630ec20cbeaddd4b65513fa3857e1b7c4190d4481ef07fb63d0fad59033321"}, + {file = "jiter-0.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:2685f44bf80e95f8910553bf2d33b9c87bf25fceae6e9f0c1355f75d2922b0ee"}, + {file = "jiter-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9ef340fae98065071ccd5805fe81c99c8f80484e820e40043689cf97fb66b3e2"}, + {file = "jiter-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb767d92c63b2cd9ec9f24feeb48f49574a713870ec87e9ba0c2c6e9329c3e2"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:113f30f87fb1f412510c6d7ed13e91422cfd329436364a690c34c8b8bd880c42"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8793b6df019b988526f5a633fdc7456ea75e4a79bd8396a3373c371fc59f5c9b"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9aaa5102dba4e079bb728076fadd5a2dca94c05c04ce68004cfd96f128ea34"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d838650f6ebaf4ccadfb04522463e74a4c378d7e667e0eb1865cfe3990bfac49"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0194f813efdf4b8865ad5f5c5f50f8566df7d770a82c51ef593d09e0b347020"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7954a401d0a8a0b8bc669199db78af435aae1e3569187c2939c477c53cb6a0a"}, + {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4feafe787eb8a8d98168ab15637ca2577f6ddf77ac6c8c66242c2d028aa5420e"}, + {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27cd1f2e8bb377f31d3190b34e4328d280325ad7ef55c6ac9abde72f79e84d2e"}, + {file = "jiter-0.9.0-cp39-cp39-win32.whl", hash = "sha256:161d461dcbe658cf0bd0aa375b30a968b087cdddc624fc585f3867c63c6eca95"}, + {file = "jiter-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e8b36d8a16a61993be33e75126ad3d8aa29cf450b09576f3c427d27647fcb4aa"}, + {file = "jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893"}, ] [[package]] @@ -2733,13 +2684,13 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" [[package]] name = "langchain-core" -version = "0.3.41" +version = "0.3.43" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_core-0.3.41-py3-none-any.whl", hash = "sha256:1a27cca5333bae7597de4004fb634b5f3e71667a3da6493b94ce83bcf15a23bd"}, - {file = "langchain_core-0.3.41.tar.gz", hash = "sha256:d3ee9f3616ebbe7943470ade23d4a04e1729b1512c0ec55a4a07bd2ac64dedb4"}, + {file = "langchain_core-0.3.43-py3-none-any.whl", hash = "sha256:caa6bc1f4c6ab71d3c2e400f8b62e1cd6dc5ac2c37e03f12f3e2c60befd5b273"}, + {file = "langchain_core-0.3.43.tar.gz", hash = "sha256:bec60f4f5665b536434ff747b8f23375a812e82cfa529f519b54cc1e7a94a875"}, ] [package.dependencies] @@ -2756,17 +2707,17 @@ typing-extensions = ">=4.7" [[package]] name = "langchain-openai" -version = "0.3.7" +version = "0.3.8" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_openai-0.3.7-py3-none-any.whl", hash = "sha256:0aefc7bdf8e7398d41e09c4313cace816df6438f2aa93d34f79523487310f0da"}, - {file = "langchain_openai-0.3.7.tar.gz", hash = "sha256:b8b51a3aaa1cc3bda060651ea41145f7728219e8a7150b5404fb1e8446de9cef"}, + {file = "langchain_openai-0.3.8-py3-none-any.whl", hash = "sha256:9004dc8ef853aece0d8f0feca7753dc97f710fa3e53874c8db66466520436dbb"}, + {file = "langchain_openai-0.3.8.tar.gz", hash = "sha256:4d73727eda8102d1d07a2ca036278fccab0bb5e0abf353cec9c3973eb72550ec"}, ] [package.dependencies] -langchain-core = ">=0.3.39,<1.0.0" +langchain-core = ">=0.3.42,<1.0.0" openai = ">=1.58.1,<2.0.0" tiktoken = ">=0.7,<1" @@ -2802,13 +2753,13 @@ types-requests = ">=2.31.0.2,<3.0.0.0" [[package]] name = "langsmith" -version = "0.3.11" +version = "0.3.13" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langsmith-0.3.11-py3-none-any.whl", hash = "sha256:0cca22737ef07d3b038a437c141deda37e00add56022582680188b681bec095e"}, - {file = "langsmith-0.3.11.tar.gz", hash = "sha256:ddf29d24352e99de79c9618aaf95679214324e146c5d3d9475a7ddd2870018b1"}, + {file = "langsmith-0.3.13-py3-none-any.whl", hash = "sha256:73aaf52bbc293b9415fff4f6dad68df40658081eb26c9cb2c7bd1ff57cedd695"}, + {file = "langsmith-0.3.13.tar.gz", hash = "sha256:14014058cff408772acb93344e03cb64174837292d5f1ae09b2c8c1d8df45e92"}, ] [package.dependencies] @@ -2829,13 +2780,13 @@ pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4,<14.0.0)"] [[package]] name = "letta-client" -version = "0.1.54" +version = "0.1.61" description = "" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "letta_client-0.1.54-py3-none-any.whl", hash = "sha256:0fda20dfdc16739e29d230e054e6840611f92fa298c5043c3d8de8f764800eaa"}, - {file = "letta_client-0.1.54.tar.gz", hash = "sha256:a1b1e200cb3e586f6e435869eecda45f26056abe44f8eee5c2547c25e2a438a2"}, + {file = "letta_client-0.1.61-py3-none-any.whl", hash = "sha256:be1eb8393709da2e38d5b06b2caeac321088d80a4f7bf89c43de545d9cbf3b2a"}, + {file = "letta_client-0.1.61.tar.gz", hash = "sha256:1f0db91be550e9b53cfbf9926b4da7d8bed03630be11d7ff29a67e855652f784"}, ] [package.dependencies] @@ -2847,13 +2798,13 @@ typing_extensions = ">=4.0.0" [[package]] name = "llama-cloud" -version = "0.1.13" +version = "0.1.14" description = "" optional = false python-versions = "<4,>=3.8" files = [ - {file = "llama_cloud-0.1.13-py3-none-any.whl", hash = "sha256:c36d9e9288cb7298faae68797f9789615b7c72409af8f3315e3098a479c09e17"}, - {file = "llama_cloud-0.1.13.tar.gz", hash = "sha256:cb6522fbd0f5e4c1cd2825e70bb943d0d8916816e232a5ce3be3a7272f329a8c"}, + {file = "llama_cloud-0.1.14-py3-none-any.whl", hash = "sha256:187672847dedc018b4d2620a8d26d6bf213e425e8b91569773de48e5c2f3ca5a"}, + {file = "llama_cloud-0.1.14.tar.gz", hash = "sha256:90741a19ba96967fa2484084928e326ae957736780a15b67bc3ad5f52e94782d"}, ] [package.dependencies] @@ -2863,37 +2814,37 @@ pydantic = ">=1.10" [[package]] name = "llama-cloud-services" -version = "0.6.3" +version = "0.6.5" description = "Tailored SDK clients for LlamaCloud services." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "llama_cloud_services-0.6.3-py3-none-any.whl", hash = "sha256:5db136268781151f318ee9bcb84c7bc7ea4a9f1b4577d4d003c5864d1c54d1af"}, - {file = "llama_cloud_services-0.6.3.tar.gz", hash = "sha256:015a5830907dc871b77ba628d1ef1d720834bfa4777c0cb821509462cdaf76aa"}, + {file = "llama_cloud_services-0.6.5-py3-none-any.whl", hash = "sha256:d9d4d0407b03249fea65aa369b110f9ee60601e4c82dba3e0a774f3a997d2b63"}, + {file = "llama_cloud_services-0.6.5.tar.gz", hash = "sha256:7c00900ed72b199d7118879637a4df060065c70cc0562f3b229e47d5f85bdb7b"}, ] [package.dependencies] click = ">=8.1.7,<9.0.0" -llama-cloud = ">=0.1.11,<0.2.0" +llama-cloud = ">=0.1.14,<0.2.0" llama-index-core = ">=0.11.0" pydantic = "!=2.10" python-dotenv = ">=1.0.1,<2.0.0" [[package]] name = "llama-index" -version = "0.12.22" +version = "0.12.23" description = "Interface between LLMs and your data" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "llama_index-0.12.22-py3-none-any.whl", hash = "sha256:a245184449954e20b791ff292536ba806027784a7e765c7c4a6bb864668d5b7d"}, - {file = "llama_index-0.12.22.tar.gz", hash = "sha256:6d9f83530cf1adefd2719509dc7ccfbda1fed92ccca030c8ffa3eb45ccc8973b"}, + {file = "llama_index-0.12.23-py3-none-any.whl", hash = "sha256:a099e06005f0e776a75b1cbac68af966bd2866c31f868d5c4f4e5be6ba641c60"}, + {file = "llama_index-0.12.23.tar.gz", hash = "sha256:af1e3b0ecb63c2e6ff95874189ca68e3fcdba19bb312abb7df19c6855cc709c0"}, ] [package.dependencies] llama-index-agent-openai = ">=0.4.0,<0.5.0" llama-index-cli = ">=0.4.1,<0.5.0" -llama-index-core = ">=0.12.22,<0.13.0" +llama-index-core = ">=0.12.23,<0.13.0" llama-index-embeddings-openai = ">=0.3.0,<0.4.0" llama-index-indices-managed-llama-cloud = ">=0.4.0" llama-index-llms-openai = ">=0.3.0,<0.4.0" @@ -2938,13 +2889,13 @@ llama-index-llms-openai = ">=0.3.0,<0.4.0" [[package]] name = "llama-index-core" -version = "0.12.22" +version = "0.12.23.post2" description = "Interface between LLMs and your data" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "llama_index_core-0.12.22-py3-none-any.whl", hash = "sha256:d238eeb26e81f89b49453bb7c3c691d19ebc89dc51a5c3ed37609a619f81bd27"}, - {file = "llama_index_core-0.12.22.tar.gz", hash = "sha256:49d4a32d0268eb719693a63ba49ce831076c2150c3cc9ed787ce1d65ecd71c0c"}, + {file = "llama_index_core-0.12.23.post2-py3-none-any.whl", hash = "sha256:3665583d69ca9859b019aacf9496af29ec2fa3b24d031344ddeeefb0dbd00e26"}, + {file = "llama_index_core-0.12.23.post2.tar.gz", hash = "sha256:b8e8abc2c11c2fa26bbfeebc79b00d8d12aaba370e43e3450045b50048744b90"}, ] [package.dependencies] @@ -3101,27 +3052,27 @@ llama-parse = ">=0.5.0" [[package]] name = "llama-parse" -version = "0.6.2" +version = "0.6.4.post1" description = "Parse files into RAG-Optimized formats." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "llama_parse-0.6.2-py3-none-any.whl", hash = "sha256:184f871858ffc15cf46c3c36e60b54cd81d014a86db97b31bfc1d8a890459e02"}, - {file = "llama_parse-0.6.2.tar.gz", hash = "sha256:5797452f2e535f886c175d19f41ce6b89cd51b190d3a8536b64a4f9dabeb8573"}, + {file = "llama_parse-0.6.4.post1-py3-none-any.whl", hash = "sha256:fdc7adb87283c2f952c830d9057c156a1349c1e6e04444d7466e732903fbc150"}, + {file = "llama_parse-0.6.4.post1.tar.gz", hash = "sha256:846d9959f4e034f8d9681dd1f003d42f8d7dc028d394ab04867b59046b4390d6"}, ] [package.dependencies] -llama-cloud-services = ">=0.6.2" +llama-cloud-services = ">=0.6.4" [[package]] name = "locust" -version = "2.33.0" +version = "2.33.1" description = "Developer-friendly load testing framework" optional = true python-versions = ">=3.9" files = [ - {file = "locust-2.33.0-py3-none-any.whl", hash = "sha256:77fcc5cc35cceee5e12d99f5bb23bc441d145bdef6967c2e93d6e4d93451553e"}, - {file = "locust-2.33.0.tar.gz", hash = "sha256:ba291b7ab2349cc2db540adb8888bc93feb89ea4e4e10d80b935e5065091e8e9"}, + {file = "locust-2.33.1-py3-none-any.whl", hash = "sha256:5a658fa65e37ea5cc0b4fb8c57055d30d86e734a4af9a00b6db7c746222896f2"}, + {file = "locust-2.33.1.tar.gz", hash = "sha256:610da1600c56a15edb11bc77370c26ba6d29f54624426c4004ca9a58c2ae38a4"}, ] [package.dependencies] @@ -3646,13 +3597,13 @@ files = [ [[package]] name = "openai" -version = "1.65.3" +version = "1.66.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" files = [ - {file = "openai-1.65.3-py3-none-any.whl", hash = "sha256:a155fa5d60eccda516384d3d60d923e083909cc126f383fe4a350f79185c232a"}, - {file = "openai-1.65.3.tar.gz", hash = "sha256:9b7cd8f79140d03d77f4ed8aeec6009be5dcd79bbc02f03b0e8cd83356004f71"}, + {file = "openai-1.66.0-py3-none-any.whl", hash = "sha256:43e4a3c0c066cc5809be4e6aac456a3ebc4ec1848226ef9d1340859ac130d45a"}, + {file = "openai-1.66.0.tar.gz", hash = "sha256:8a9e672bc6eadec60a962f0b40d7d1c09050010179c919ed65322e433e2d1025"}, ] [package.dependencies] @@ -4486,7 +4437,6 @@ files = [ {file = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"}, {file = "psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"}, {file = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"}, - {file = "psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2"}, {file = "psycopg2-2.9.10-cp39-cp39-win32.whl", hash = "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"}, {file = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"}, {file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}, @@ -4546,7 +4496,6 @@ files = [ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, @@ -5115,29 +5064,27 @@ files = [ [[package]] name = "pywin32" -version = "308" +version = "309" description = "Python for Window Extensions" optional = false python-versions = "*" files = [ - {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, - {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, - {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, - {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, - {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, - {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, - {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, - {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, - {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, - {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, - {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, - {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, - {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, - {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, - {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, - {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, - {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, - {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, + {file = "pywin32-309-cp310-cp310-win32.whl", hash = "sha256:5b78d98550ca093a6fe7ab6d71733fbc886e2af9d4876d935e7f6e1cd6577ac9"}, + {file = "pywin32-309-cp310-cp310-win_amd64.whl", hash = "sha256:728d08046f3d65b90d4c77f71b6fbb551699e2005cc31bbffd1febd6a08aa698"}, + {file = "pywin32-309-cp310-cp310-win_arm64.whl", hash = "sha256:c667bcc0a1e6acaca8984eb3e2b6e42696fc035015f99ff8bc6c3db4c09a466a"}, + {file = "pywin32-309-cp311-cp311-win32.whl", hash = "sha256:d5df6faa32b868baf9ade7c9b25337fa5eced28eb1ab89082c8dae9c48e4cd51"}, + {file = "pywin32-309-cp311-cp311-win_amd64.whl", hash = "sha256:e7ec2cef6df0926f8a89fd64959eba591a1eeaf0258082065f7bdbe2121228db"}, + {file = "pywin32-309-cp311-cp311-win_arm64.whl", hash = "sha256:54ee296f6d11db1627216e9b4d4c3231856ed2d9f194c82f26c6cb5650163f4c"}, + {file = "pywin32-309-cp312-cp312-win32.whl", hash = "sha256:de9acacced5fa82f557298b1fed5fef7bd49beee04190f68e1e4783fbdc19926"}, + {file = "pywin32-309-cp312-cp312-win_amd64.whl", hash = "sha256:6ff9eebb77ffc3d59812c68db33c0a7817e1337e3537859499bd27586330fc9e"}, + {file = "pywin32-309-cp312-cp312-win_arm64.whl", hash = "sha256:619f3e0a327b5418d833f44dc87859523635cf339f86071cc65a13c07be3110f"}, + {file = "pywin32-309-cp313-cp313-win32.whl", hash = "sha256:008bffd4afd6de8ca46c6486085414cc898263a21a63c7f860d54c9d02b45c8d"}, + {file = "pywin32-309-cp313-cp313-win_amd64.whl", hash = "sha256:bd0724f58492db4cbfbeb1fcd606495205aa119370c0ddc4f70e5771a3ab768d"}, + {file = "pywin32-309-cp313-cp313-win_arm64.whl", hash = "sha256:8fd9669cfd41863b688a1bc9b1d4d2d76fd4ba2128be50a70b0ea66b8d37953b"}, + {file = "pywin32-309-cp38-cp38-win32.whl", hash = "sha256:617b837dc5d9dfa7e156dbfa7d3906c009a2881849a80a9ae7519f3dd8c6cb86"}, + {file = "pywin32-309-cp38-cp38-win_amd64.whl", hash = "sha256:0be3071f555480fbfd86a816a1a773880ee655bf186aa2931860dbb44e8424f8"}, + {file = "pywin32-309-cp39-cp39-win32.whl", hash = "sha256:72ae9ae3a7a6473223589a1621f9001fe802d59ed227fd6a8503c9af67c1d5f4"}, + {file = "pywin32-309-cp39-cp39-win_amd64.whl", hash = "sha256:88bc06d6a9feac70783de64089324568ecbc65866e2ab318eab35da3811fd7ef"}, ] [[package]] @@ -5349,13 +5296,13 @@ fastembed-gpu = ["fastembed-gpu (==0.3.6)"] [[package]] name = "qdrant-client" -version = "1.13.2" +version = "1.13.3" description = "Client library for the Qdrant vector search engine" optional = true python-versions = ">=3.9" files = [ - {file = "qdrant_client-1.13.2-py3-none-any.whl", hash = "sha256:db97e759bd3f8d483a383984ba4c2a158eef56f2188d83df7771591d43de2201"}, - {file = "qdrant_client-1.13.2.tar.gz", hash = "sha256:c8cce87ce67b006f49430a050a35c85b78e3b896c0c756dafc13bdeca543ec13"}, + {file = "qdrant_client-1.13.3-py3-none-any.whl", hash = "sha256:f52cacbb936e547d3fceb1aaed3e3c56be0ebfd48e8ea495ea3dbc89c671d1d2"}, + {file = "qdrant_client-1.13.3.tar.gz", hash = "sha256:61ca09e07c6d7ac0dfbdeb13dca4fe5f3e08fa430cb0d74d66ef5d023a70adfc"}, ] [package.dependencies] @@ -5364,7 +5311,7 @@ grpcio-tools = ">=1.41.0" httpx = {version = ">=0.20.0", extras = ["http2"]} numpy = [ {version = ">=1.21", markers = "python_version >= \"3.10\" and python_version < \"3.12\""}, - {version = ">=1.26", markers = "python_version >= \"3.12\" and python_version < \"3.13\""}, + {version = ">=1.26", markers = "python_version == \"3.12\""}, ] portalocker = ">=2.7.0,<3.0.0" pydantic = ">=1.10.8" @@ -5845,68 +5792,12 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.38" +version = "2.0.39" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6"}, - {file = "SQLAlchemy-2.0.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444"}, - {file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:402c2316d95ed90d3d3c25ad0390afa52f4d2c56b348f212aa9c8d072a40eee5"}, - {file = "SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6493bc0eacdbb2c0f0d260d8988e943fee06089cd239bd7f3d0c45d1657a70e2"}, - {file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0561832b04c6071bac3aad45b0d3bb6d2c4f46a8409f0a7a9c9fa6673b41bc03"}, - {file = "SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:49aa2cdd1e88adb1617c672a09bf4ebf2f05c9448c6dbeba096a3aeeb9d4d443"}, - {file = "SQLAlchemy-2.0.38-cp310-cp310-win32.whl", hash = "sha256:64aa8934200e222f72fcfd82ee71c0130a9c07d5725af6fe6e919017d095b297"}, - {file = "SQLAlchemy-2.0.38-cp310-cp310-win_amd64.whl", hash = "sha256:c57b8e0841f3fce7b703530ed70c7c36269c6d180ea2e02e36b34cb7288c50c7"}, - {file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf89e0e4a30714b357f5d46b6f20e0099d38b30d45fa68ea48589faf5f12f62d"}, - {file = "SQLAlchemy-2.0.38-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8455aa60da49cb112df62b4721bd8ad3654a3a02b9452c783e651637a1f21fa2"}, - {file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53c0d6a859b2db58332e0e6a921582a02c1677cc93d4cbb36fdf49709b327b2"}, - {file = "SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c4817dff8cef5697f5afe5fec6bc1783994d55a68391be24cb7d80d2dbc3a6"}, - {file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9cea5b756173bb86e2235f2f871b406a9b9d722417ae31e5391ccaef5348f2c"}, - {file = "SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e9cdbd18c1f84631312b64993f7d755d85a3930252f6276a77432a2b25a2f3"}, - {file = "SQLAlchemy-2.0.38-cp311-cp311-win32.whl", hash = "sha256:cb39ed598aaf102251483f3e4675c5dd6b289c8142210ef76ba24aae0a8f8aba"}, - {file = "SQLAlchemy-2.0.38-cp311-cp311-win_amd64.whl", hash = "sha256:f9d57f1b3061b3e21476b0ad5f0397b112b94ace21d1f439f2db472e568178ae"}, - {file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12d5b06a1f3aeccf295a5843c86835033797fea292c60e72b07bcb5d820e6dd3"}, - {file = "SQLAlchemy-2.0.38-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e036549ad14f2b414c725349cce0772ea34a7ab008e9cd67f9084e4f371d1f32"}, - {file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3bee874cb1fadee2ff2b79fc9fc808aa638670f28b2145074538d4a6a5028e"}, - {file = "SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185ea07a99ce8b8edfc788c586c538c4b1351007e614ceb708fd01b095ef33e"}, - {file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b79ee64d01d05a5476d5cceb3c27b5535e6bb84ee0f872ba60d9a8cd4d0e6579"}, - {file = "SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afd776cf1ebfc7f9aa42a09cf19feadb40a26366802d86c1fba080d8e5e74bdd"}, - {file = "SQLAlchemy-2.0.38-cp312-cp312-win32.whl", hash = "sha256:a5645cd45f56895cfe3ca3459aed9ff2d3f9aaa29ff7edf557fa7a23515a3725"}, - {file = "SQLAlchemy-2.0.38-cp312-cp312-win_amd64.whl", hash = "sha256:1052723e6cd95312f6a6eff9a279fd41bbae67633415373fdac3c430eca3425d"}, - {file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ecef029b69843b82048c5b347d8e6049356aa24ed644006c9a9d7098c3bd3bfd"}, - {file = "SQLAlchemy-2.0.38-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c8bcad7fc12f0cc5896d8e10fdf703c45bd487294a986903fe032c72201596b"}, - {file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0ef3f98175d77180ffdc623d38e9f1736e8d86b6ba70bff182a7e68bed7727"}, - {file = "SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ac78898c50e2574e9f938d2e5caa8fe187d7a5b69b65faa1ea4648925b096"}, - {file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9eb4fa13c8c7a2404b6a8e3772c17a55b1ba18bc711e25e4d6c0c9f5f541b02a"}, - {file = "SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dba1cdb8f319084f5b00d41207b2079822aa8d6a4667c0f369fce85e34b0c86"}, - {file = "SQLAlchemy-2.0.38-cp313-cp313-win32.whl", hash = "sha256:eae27ad7580529a427cfdd52c87abb2dfb15ce2b7a3e0fc29fbb63e2ed6f8120"}, - {file = "SQLAlchemy-2.0.38-cp313-cp313-win_amd64.whl", hash = "sha256:b335a7c958bc945e10c522c069cd6e5804f4ff20f9a744dd38e748eb602cbbda"}, - {file = "SQLAlchemy-2.0.38-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:40310db77a55512a18827488e592965d3dec6a3f1e3d8af3f8243134029daca3"}, - {file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3043375dd5bbcb2282894cbb12e6c559654c67b5fffb462fda815a55bf93f7"}, - {file = "SQLAlchemy-2.0.38-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70065dfabf023b155a9c2a18f573e47e6ca709b9e8619b2e04c54d5bcf193178"}, - {file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c058b84c3b24812c859300f3b5abf300daa34df20d4d4f42e9652a4d1c48c8a4"}, - {file = "SQLAlchemy-2.0.38-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0398361acebb42975deb747a824b5188817d32b5c8f8aba767d51ad0cc7bb08d"}, - {file = "SQLAlchemy-2.0.38-cp37-cp37m-win32.whl", hash = "sha256:a2bc4e49e8329f3283d99840c136ff2cd1a29e49b5624a46a290f04dff48e079"}, - {file = "SQLAlchemy-2.0.38-cp37-cp37m-win_amd64.whl", hash = "sha256:9cd136184dd5f58892f24001cdce986f5d7e96059d004118d5410671579834a4"}, - {file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:665255e7aae5f38237b3a6eae49d2358d83a59f39ac21036413fab5d1e810578"}, - {file = "SQLAlchemy-2.0.38-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:92f99f2623ff16bd4aaf786ccde759c1f676d39c7bf2855eb0b540e1ac4530c8"}, - {file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa498d1392216fae47eaf10c593e06c34476ced9549657fca713d0d1ba5f7248"}, - {file = "SQLAlchemy-2.0.38-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9afbc3909d0274d6ac8ec891e30210563b2c8bdd52ebbda14146354e7a69373"}, - {file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:57dd41ba32430cbcc812041d4de8d2ca4651aeefad2626921ae2a23deb8cd6ff"}, - {file = "SQLAlchemy-2.0.38-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3e35d5565b35b66905b79ca4ae85840a8d40d31e0b3e2990f2e7692071b179ca"}, - {file = "SQLAlchemy-2.0.38-cp38-cp38-win32.whl", hash = "sha256:f0d3de936b192980209d7b5149e3c98977c3810d401482d05fb6d668d53c1c63"}, - {file = "SQLAlchemy-2.0.38-cp38-cp38-win_amd64.whl", hash = "sha256:3868acb639c136d98107c9096303d2d8e5da2880f7706f9f8c06a7f961961149"}, - {file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07258341402a718f166618470cde0c34e4cec85a39767dce4e24f61ba5e667ea"}, - {file = "SQLAlchemy-2.0.38-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a826f21848632add58bef4f755a33d45105d25656a0c849f2dc2df1c71f6f50"}, - {file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:386b7d136919bb66ced64d2228b92d66140de5fefb3c7df6bd79069a269a7b06"}, - {file = "SQLAlchemy-2.0.38-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f2951dc4b4f990a4b394d6b382accb33141d4d3bd3ef4e2b27287135d6bdd68"}, - {file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8bf312ed8ac096d674c6aa9131b249093c1b37c35db6a967daa4c84746bc1bc9"}, - {file = "SQLAlchemy-2.0.38-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6db316d6e340f862ec059dc12e395d71f39746a20503b124edc255973977b728"}, - {file = "SQLAlchemy-2.0.38-cp39-cp39-win32.whl", hash = "sha256:c09a6ea87658695e527104cf857c70f79f14e9484605e205217aae0ec27b45fc"}, - {file = "SQLAlchemy-2.0.38-cp39-cp39-win_amd64.whl", hash = "sha256:12f5c9ed53334c3ce719155424dc5407aaa4f6cadeb09c5b627e06abb93933a1"}, - {file = "SQLAlchemy-2.0.38-py3-none-any.whl", hash = "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753"}, - {file = "sqlalchemy-2.0.38.tar.gz", hash = "sha256:e5a4d82bdb4bf1ac1285a68eab02d253ab73355d9f0fe725a97e1e0fa689decb"}, + {file = "sqlalchemy-2.0.39.tar.gz", hash = "sha256:5d2d1fe548def3267b4c70a8568f108d1fed7cbbeccb9cc166e05af2abc25c22"}, ] [package.dependencies] @@ -6019,13 +5910,13 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "starlette" -version = "0.46.0" +version = "0.46.1" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" files = [ - {file = "starlette-0.46.0-py3-none-any.whl", hash = "sha256:913f0798bd90ba90a9156383bcf1350a17d6259451d0d8ee27fc0cf2db609038"}, - {file = "starlette-0.46.0.tar.gz", hash = "sha256:b359e4567456b28d473d0193f34c0de0ed49710d75ef183a74a5ce0499324f50"}, + {file = "starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227"}, + {file = "starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230"}, ] [package.dependencies] @@ -6252,13 +6143,13 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. [[package]] name = "types-requests" -version = "2.32.0.20250301" +version = "2.32.0.20250306" description = "Typing stubs for requests" optional = false python-versions = ">=3.9" files = [ - {file = "types_requests-2.32.0.20250301-py3-none-any.whl", hash = "sha256:0003e0124e2cbefefb88222ff822b48616af40c74df83350f599a650c8de483b"}, - {file = "types_requests-2.32.0.20250301.tar.gz", hash = "sha256:3d909dc4eaab159c0d964ebe8bfa326a7afb4578d8706408d417e17d61b0c500"}, + {file = "types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b"}, + {file = "types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1"}, ] [package.dependencies] @@ -6339,13 +6230,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.29.2" +version = "20.29.3" description = "Virtual Python Environment builder" optional = true python-versions = ">=3.8" files = [ - {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"}, - {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"}, + {file = "virtualenv-20.29.3-py3-none-any.whl", hash = "sha256:3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170"}, + {file = "virtualenv-20.29.3.tar.gz", hash = "sha256:95e39403fcf3940ac45bc717597dba16110b74506131845d9b687d5e73d947ac"}, ] [package.dependencies] @@ -7013,9 +6904,10 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\ cffi = ["cffi (>=1.11)"] [extras] -all = ["autoflake", "black", "datasets", "docker", "fastapi", "isort", "langchain", "langchain-community", "locust", "pexpect", "pg8000", "pgvector", "pre-commit", "psycopg2", "psycopg2-binary", "pyright", "pytest-asyncio", "pytest-order", "uvicorn", "wikipedia"] +all = ["autoflake", "black", "datamodel-code-generator", "datasets", "docker", "fastapi", "isort", "langchain", "langchain-community", "locust", "pexpect", "pg8000", "pgvector", "pre-commit", "psycopg2", "psycopg2-binary", "pyright", "pytest-asyncio", "pytest-order", "uvicorn", "wikipedia"] bedrock = ["boto3"] cloud-tool-sandbox = ["e2b-code-interpreter"] +desktop = ["datamodel-code-generator", "datasets", "docker", "fastapi", "langchain", "langchain-community", "locust", "pg8000", "pgvector", "psycopg2", "psycopg2-binary", "pyright", "uvicorn", "wikipedia"] dev = ["autoflake", "black", "datasets", "isort", "locust", "pexpect", "pre-commit", "pyright", "pytest-asyncio", "pytest-order"] external-tools = ["docker", "langchain", "langchain-community", "wikipedia"] google = ["google-genai"] @@ -7027,4 +6919,4 @@ tests = ["wikipedia"] [metadata] lock-version = "2.0" python-versions = "<3.14,>=3.10" -content-hash = "6e26e41880144950187a1488a458908bf2835d2aa0a490c822a338d7fc4cf3db" +content-hash = "035bcd8608846fc1a9956d8fc204914d6a20280c20dbf0c6ec4680767c7b8c97" diff --git a/pyproject.toml b/pyproject.toml index 3393b21e..0909e998 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "letta" -version = "0.6.37" +version = "0.6.38" packages = [ {include = "letta"}, ] @@ -58,8 +58,8 @@ nltk = "^3.8.1" jinja2 = "^3.1.5" locust = {version = "^2.31.5", optional = true} wikipedia = {version = "^1.4.0", optional = true} -composio-langchain = "^0.7.2" -composio-core = "^0.7.2" +composio-langchain = "^0.7.7" +composio-core = "^0.7.7" alembic = "^1.13.3" pyhumps = "^3.8.0" psycopg2 = {version = "^2.9.10", optional = true} @@ -98,9 +98,10 @@ qdrant = ["qdrant-client"] cloud-tool-sandbox = ["e2b-code-interpreter"] external-tools = ["docker", "langchain", "wikipedia", "langchain-community"] tests = ["wikipedia"] -all = ["pgvector", "pg8000", "psycopg2-binary", "psycopg2", "pytest", "pytest-asyncio", "pexpect", "black", "pre-commit", "datasets", "pyright", "pytest-order", "autoflake", "isort", "websockets", "fastapi", "uvicorn", "docker", "langchain", "wikipedia", "langchain-community", "locust"] bedrock = ["boto3"] google = ["google-genai"] +desktop = ["pgvector", "pg8000", "psycopg2-binary", "psycopg2", "datasets", "pyright", "websockets", "fastapi", "uvicorn", "docker", "langchain", "wikipedia", "langchain-community", "locust", "datamodel-code-generator"] +all = ["pgvector", "pg8000", "psycopg2-binary", "psycopg2", "pytest", "pytest-asyncio", "pexpect", "black", "pre-commit", "datasets", "pyright", "pytest-order", "autoflake", "isort", "websockets", "fastapi", "uvicorn", "docker", "langchain", "wikipedia", "langchain-community", "locust", "datamodel-code-generator"] [tool.poetry.group.dev.dependencies] black = "^24.4.2" diff --git a/tests/helpers/endpoints_helper.py b/tests/helpers/endpoints_helper.py index 2c721262..c487d07a 100644 --- a/tests/helpers/endpoints_helper.py +++ b/tests/helpers/endpoints_helper.py @@ -17,6 +17,7 @@ from letta.embeddings import embedding_model from letta.errors import InvalidInnerMonologueError, InvalidToolCallError, MissingInnerMonologueError, MissingToolCallError from letta.helpers.json_helpers import json_dumps from letta.llm_api.llm_api_tools import create +from letta.llm_api.llm_client import LLMClient from letta.local_llm.constants import INNER_THOUGHTS_KWARG from letta.schemas.agent import AgentState from letta.schemas.embedding_config import EmbeddingConfig @@ -103,12 +104,23 @@ def check_first_response_is_valid_for_llm_endpoint(filename: str, validate_inner messages = client.server.agent_manager.get_in_context_messages(agent_id=full_agent_state.id, actor=client.user) agent = Agent(agent_state=full_agent_state, interface=None, user=client.user) - response = create( + llm_client = LLMClient.create( + agent_id=agent_state.id, llm_config=agent_state.llm_config, - user_id=str(uuid.UUID(int=1)), # dummy user_id - messages=messages, - functions=[t.json_schema for t in agent.agent_state.tools], + actor_id=str(uuid.UUID(int=1)), ) + if llm_client: + response = llm_client.send_llm_request( + messages=messages, + tools=[t.json_schema for t in agent.agent_state.tools], + ) + else: + response = create( + llm_config=agent_state.llm_config, + user_id=str(uuid.UUID(int=1)), # dummy user_id + messages=messages, + functions=[t.json_schema for t in agent.agent_state.tools], + ) # Basic check assert response is not None, response diff --git a/tests/integration_test_chat_completions.py b/tests/integration_test_chat_completions.py index 97320849..3ae24ec6 100644 --- a/tests/integration_test_chat_completions.py +++ b/tests/integration_test_chat_completions.py @@ -120,12 +120,11 @@ def agent(client, roll_dice_tool, weather_tool, composio_gmail_get_profile_tool) # --- Helper Functions --- # -def _get_chat_request(agent_id, message, stream=True): +def _get_chat_request(message, stream=True): """Returns a chat completion request with streaming enabled.""" return ChatCompletionRequest( model="gpt-4o-mini", messages=[UserMessage(content=message)], - user=agent_id, stream=stream, ) @@ -157,9 +156,9 @@ def _assert_valid_chunk(chunk, idx, chunks): @pytest.mark.parametrize("endpoint", ["v1/voice"]) async def test_latency(mock_e2b_api_key_none, client, agent, message, endpoint): """Tests chat completion streaming using the Async OpenAI client.""" - request = _get_chat_request(agent.id, message) + request = _get_chat_request(message) - async_client = AsyncOpenAI(base_url=f"{client.base_url}/{endpoint}", max_retries=0) + async_client = AsyncOpenAI(base_url=f"{client.base_url}/{endpoint}/{agent.id}", max_retries=0) stream = await async_client.chat.completions.create(**request.model_dump(exclude_none=True)) async with stream: async for chunk in stream: @@ -171,9 +170,9 @@ async def test_latency(mock_e2b_api_key_none, client, agent, message, endpoint): @pytest.mark.parametrize("endpoint", ["openai/v1", "v1/voice"]) async def test_chat_completions_streaming_openai_client(mock_e2b_api_key_none, client, agent, message, endpoint): """Tests chat completion streaming using the Async OpenAI client.""" - request = _get_chat_request(agent.id, message) + request = _get_chat_request(message) - async_client = AsyncOpenAI(base_url=f"{client.base_url}/{endpoint}", max_retries=0) + async_client = AsyncOpenAI(base_url=f"{client.base_url}/{endpoint}/{agent.id}", max_retries=0) stream = await async_client.chat.completions.create(**request.model_dump(exclude_none=True)) received_chunks = 0 diff --git a/tests/integration_test_multi_agent.py b/tests/integration_test_multi_agent.py index 91df2e24..30413d69 100644 --- a/tests/integration_test_multi_agent.py +++ b/tests/integration_test_multi_agent.py @@ -127,54 +127,55 @@ def test_send_message_to_agent(client, agent_obj, other_agent_obj): @retry_until_success(max_attempts=3, sleep_time_seconds=2) def test_send_message_to_agents_with_tags_simple(client): - worker_tags = ["worker", "user-456"] + worker_tags_123 = ["worker", "user-123"] + worker_tags_456 = ["worker", "user-456"] # Clean up first from possibly failed tests - prev_worker_agents = client.server.agent_manager.list_agents(client.user, tags=worker_tags, match_all_tags=True) + prev_worker_agents = client.server.agent_manager.list_agents( + client.user, tags=list(set(worker_tags_123 + worker_tags_456)), match_all_tags=True + ) for agent in prev_worker_agents: client.delete_agent(agent.id) secret_word = "banana" # Create "manager" agent - send_message_to_agents_matching_all_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_all_tags") - manager_agent_state = client.create_agent(tool_ids=[send_message_to_agents_matching_all_tags_tool_id]) + send_message_to_agents_matching_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_tags") + manager_agent_state = client.create_agent(tool_ids=[send_message_to_agents_matching_tags_tool_id]) manager_agent = client.server.load_agent(agent_id=manager_agent_state.id, actor=client.user) # Create 3 non-matching worker agents (These should NOT get the message) - worker_agents = [] - worker_tags = ["worker", "user-123"] + worker_agents_123 = [] for _ in range(3): - worker_agent_state = client.create_agent(include_multi_agent_tools=False, tags=worker_tags) + worker_agent_state = client.create_agent(include_multi_agent_tools=False, tags=worker_tags_123) worker_agent = client.server.load_agent(agent_id=worker_agent_state.id, actor=client.user) - worker_agents.append(worker_agent) + worker_agents_123.append(worker_agent) # Create 3 worker agents that should get the message - worker_agents = [] - worker_tags = ["worker", "user-456"] + worker_agents_456 = [] for _ in range(3): - worker_agent_state = client.create_agent(include_multi_agent_tools=False, tags=worker_tags) + worker_agent_state = client.create_agent(include_multi_agent_tools=False, tags=worker_tags_456) worker_agent = client.server.load_agent(agent_id=worker_agent_state.id, actor=client.user) - worker_agents.append(worker_agent) + worker_agents_456.append(worker_agent) # Encourage the manager to send a message to the other agent_obj with the secret string response = client.send_message( agent_id=manager_agent.agent_state.id, role="user", - message=f"Send a message to all agents with tags {worker_tags} informing them of the secret word: {secret_word}!", + message=f"Send a message to all agents with tags {worker_tags_456} informing them of the secret word: {secret_word}!", ) for m in response.messages: if isinstance(m, ToolReturnMessage): tool_response = eval(json.loads(m.tool_return)["message"]) print(f"\n\nManager agent tool response: \n{tool_response}\n\n") - assert len(tool_response) == len(worker_agents) + assert len(tool_response) == len(worker_agents_456) # We can break after this, the ToolReturnMessage after is not related break # Conversation search the worker agents - for agent in worker_agents: + for agent in worker_agents_456: messages = client.get_messages(agent.agent_state.id) # Check for the presence of system message for m in reversed(messages): @@ -183,13 +184,22 @@ def test_send_message_to_agents_with_tags_simple(client): assert secret_word in m.content break + # Ensure it's NOT in the non matching worker agents + for agent in worker_agents_123: + messages = client.get_messages(agent.agent_state.id) + # Check for the presence of system message + for m in reversed(messages): + print(f"\n\n {agent.agent_state.id} -> {m.model_dump_json(indent=4)}") + if isinstance(m, SystemMessage): + assert secret_word not in m.content + # Test that the agent can still receive messages fine response = client.send_message(agent_id=manager_agent.agent_state.id, role="user", message="So what did the other agents say?") print("Manager agent followup message: \n\n" + "\n".join([str(m) for m in response.messages])) # Clean up agents client.delete_agent(manager_agent_state.id) - for agent in worker_agents: + for agent in worker_agents_456 + worker_agents_123: client.delete_agent(agent.agent_state.id) @@ -203,8 +213,8 @@ def test_send_message_to_agents_with_tags_complex_tool_use(client, roll_dice_too client.delete_agent(agent.id) # Create "manager" agent - send_message_to_agents_matching_all_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_all_tags") - manager_agent_state = client.create_agent(tool_ids=[send_message_to_agents_matching_all_tags_tool_id]) + send_message_to_agents_matching_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_tags") + manager_agent_state = client.create_agent(tool_ids=[send_message_to_agents_matching_tags_tool_id]) manager_agent = client.server.load_agent(agent_id=manager_agent_state.id, actor=client.user) # Create 3 worker agents @@ -245,8 +255,8 @@ def test_send_message_to_agents_with_tags_complex_tool_use(client, roll_dice_too @retry_until_success(max_attempts=3, sleep_time_seconds=2) def test_send_message_to_sub_agents_auto_clear_message_buffer(client): # Create "manager" agent - send_message_to_agents_matching_all_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_all_tags") - manager_agent_state = client.create_agent(name="manager", tool_ids=[send_message_to_agents_matching_all_tags_tool_id]) + send_message_to_agents_matching_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_tags") + manager_agent_state = client.create_agent(name="manager", tool_ids=[send_message_to_agents_matching_tags_tool_id]) manager_agent = client.server.load_agent(agent_id=manager_agent_state.id, actor=client.user) # Create 2 worker agents @@ -260,7 +270,7 @@ def test_send_message_to_sub_agents_auto_clear_message_buffer(client): worker_agents.append(worker_agent) # Encourage the manager to send a message to the other agent_obj with the secret string - broadcast_message = f"Using your tool named `send_message_to_agents_matching_all_tags`, instruct all agents with tags {worker_tags} to `core_memory_append` the topic of the day: bananas!" + broadcast_message = f"Using your tool named `send_message_to_agents_matching_tags`, instruct all agents with tags {worker_tags} to `core_memory_append` the topic of the day: bananas!" client.send_message( agent_id=manager_agent.agent_state.id, role="user", diff --git a/tests/manual_test_multi_agent_broadcast_large.py b/tests/manual_test_multi_agent_broadcast_large.py index 2108f03a..70d88f44 100644 --- a/tests/manual_test_multi_agent_broadcast_large.py +++ b/tests/manual_test_multi_agent_broadcast_large.py @@ -65,10 +65,8 @@ def test_multi_agent_large(client, roll_dice_tool, num_workers): client.delete_agent(agent.id) # Create "manager" agent - send_message_to_agents_matching_all_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_all_tags") - manager_agent_state = client.create_agent( - name="manager", tool_ids=[send_message_to_agents_matching_all_tags_tool_id], tags=manager_tags - ) + send_message_to_agents_matching_tags_tool_id = client.get_tool_id(name="send_message_to_agents_matching_tags") + manager_agent_state = client.create_agent(name="manager", tool_ids=[send_message_to_agents_matching_tags_tool_id], tags=manager_tags) manager_agent = client.server.load_agent(agent_id=manager_agent_state.id, actor=client.user) # Create 3 worker agents diff --git a/tests/test_agent_serialization.py b/tests/test_agent_serialization.py index facbd552..b7f6cc7d 100644 --- a/tests/test_agent_serialization.py +++ b/tests/test_agent_serialization.py @@ -229,7 +229,7 @@ def _compare_agent_state_model_dump(d1: Dict[str, Any], d2: Dict[str, Any], log: - Datetime fields are ignored. - Order-independent comparison for lists of dicts. """ - ignore_prefix_fields = {"id", "last_updated_by_id", "organization_id", "created_by_id", "agent_id"} + ignore_prefix_fields = {"id", "last_updated_by_id", "organization_id", "created_by_id", "agent_id", "project_id"} # Remove datetime fields upfront d1 = strip_datetime_fields(d1) @@ -476,8 +476,9 @@ def test_agent_serialize_tool_calls(mock_e2b_api_key_none, local_client, server, # FastAPI endpoint tests -@pytest.mark.parametrize("append_copy_suffix", [True]) -def test_agent_download_upload_flow(fastapi_client, server, serialize_test_agent, default_user, other_user, append_copy_suffix): +@pytest.mark.parametrize("append_copy_suffix", [True, False]) +@pytest.mark.parametrize("project_id", ["project-12345", None]) +def test_agent_download_upload_flow(fastapi_client, server, serialize_test_agent, default_user, other_user, append_copy_suffix, project_id): """ Test the full E2E serialization and deserialization flow using FastAPI endpoints. """ @@ -495,7 +496,7 @@ def test_agent_download_upload_flow(fastapi_client, server, serialize_test_agent upload_response = fastapi_client.post( "/v1/agents/upload", headers={"user_id": other_user.id}, - params={"append_copy_suffix": append_copy_suffix, "override_existing_tools": False}, + params={"append_copy_suffix": append_copy_suffix, "override_existing_tools": False, "project_id": project_id}, files=files, ) assert upload_response.status_code == 200, f"Upload failed: {upload_response.text}" @@ -504,7 +505,8 @@ def test_agent_download_upload_flow(fastapi_client, server, serialize_test_agent copied_agent = upload_response.json() copied_agent_id = copied_agent["id"] assert copied_agent_id != agent_id, "Copied agent should have a different ID" - assert copied_agent["name"] == serialize_test_agent.name + "_copy", "Copied agent name should have '_copy' suffix" + if append_copy_suffix: + assert copied_agent["name"] == serialize_test_agent.name + "_copy", "Copied agent name should have '_copy' suffix" # Step 3: Retrieve the copied agent serialize_test_agent = server.agent_manager.get_agent_by_id(agent_id=serialize_test_agent.id, actor=default_user) diff --git a/tests/test_client_legacy.py b/tests/test_client_legacy.py index 00ee65ba..3a744fc2 100644 --- a/tests/test_client_legacy.py +++ b/tests/test_client_legacy.py @@ -26,7 +26,7 @@ from letta.schemas.letta_message import ( ToolReturnMessage, UserMessage, ) -from letta.schemas.letta_response import LettaResponse, LettaStreamingResponse +from letta.schemas.letta_response import LettaStreamingResponse from letta.schemas.llm_config import LLMConfig from letta.schemas.message import MessageCreate from letta.schemas.usage import LettaUsageStatistics @@ -536,21 +536,6 @@ def test_sources(client: Union[LocalClient, RESTClient], agent: AgentState): client.delete_source(source.id) -def test_message_update(client: Union[LocalClient, RESTClient], agent: AgentState): - """Test that we can update the details of a message""" - - # create a message - message_response = client.send_message(agent_id=agent.id, message="Test message", role="user") - print("Messages=", message_response) - assert isinstance(message_response, LettaResponse) - assert isinstance(message_response.messages[-1], AssistantMessage) - message = message_response.messages[-1] - - new_text = "this is a secret message" - new_message = client.update_message(message_id=message.id, text=new_text, agent_id=agent.id) - assert new_message.text == new_text - - def test_organization(client: RESTClient): if isinstance(client, LocalClient): pytest.skip("Skipping test_organization because LocalClient does not support organizations") diff --git a/tests/test_managers.py b/tests/test_managers.py index 334e72ed..49744d62 100644 --- a/tests/test_managers.py +++ b/tests/test_managers.py @@ -21,8 +21,10 @@ from letta.schemas.embedding_config import EmbeddingConfig from letta.schemas.enums import JobStatus, MessageRole from letta.schemas.environment_variables import SandboxEnvironmentVariableCreate, SandboxEnvironmentVariableUpdate from letta.schemas.file import FileMetadata as PydanticFileMetadata +from letta.schemas.identity import IdentityCreate, IdentityProperty, IdentityPropertyType, IdentityType, IdentityUpdate from letta.schemas.job import Job as PydanticJob from letta.schemas.job import JobUpdate, LettaRequestConfig +from letta.schemas.letta_message import UpdateAssistantMessage, UpdateReasoningMessage, UpdateSystemMessage, UpdateUserMessage from letta.schemas.llm_config import LLMConfig from letta.schemas.message import Message as PydanticMessage from letta.schemas.message import MessageCreate, MessageUpdate @@ -40,6 +42,7 @@ from letta.schemas.user import User as PydanticUser from letta.schemas.user import UserUpdate from letta.server.server import SyncServer from letta.services.block_manager import BlockManager +from letta.services.identity_manager import IdentityManager from letta.services.organization_manager import OrganizationManager from letta.settings import tool_settings from tests.helpers.utils import comprehensive_agent_checks @@ -472,6 +475,45 @@ def agent_passages_setup(server, default_source, default_user, sarah_agent): server.source_manager.delete_source(default_source.id, actor=actor) +@pytest.fixture +def agent_with_tags(server: SyncServer, default_user): + """Fixture to create agents with specific tags.""" + agent1 = server.agent_manager.create_agent( + agent_create=CreateAgent( + name="agent1", + tags=["primary_agent", "benefit_1"], + llm_config=LLMConfig.default_config("gpt-4o-mini"), + embedding_config=EmbeddingConfig.default_config(provider="openai"), + memory_blocks=[], + ), + actor=default_user, + ) + + agent2 = server.agent_manager.create_agent( + agent_create=CreateAgent( + name="agent2", + tags=["primary_agent", "benefit_2"], + llm_config=LLMConfig.default_config("gpt-4o-mini"), + embedding_config=EmbeddingConfig.default_config(provider="openai"), + memory_blocks=[], + ), + actor=default_user, + ) + + agent3 = server.agent_manager.create_agent( + agent_create=CreateAgent( + name="agent3", + tags=["primary_agent", "benefit_1", "benefit_2"], + llm_config=LLMConfig.default_config("gpt-4o-mini"), + embedding_config=EmbeddingConfig.default_config(provider="openai"), + memory_blocks=[], + ), + actor=default_user, + ) + + return [agent1, agent2, agent3] + + # ====================================================================================================================== # AgentManager Tests - Basic # ====================================================================================================================== @@ -775,6 +817,45 @@ def test_list_attached_agents_nonexistent_source(server: SyncServer, default_use # ====================================================================================================================== +def test_list_agents_matching_all_tags(server: SyncServer, default_user, agent_with_tags): + agents = server.agent_manager.list_agents_matching_tags( + actor=default_user, + match_all=["primary_agent", "benefit_1"], + match_some=[], + ) + assert len(agents) == 2 # agent1 and agent3 match + assert {a.name for a in agents} == {"agent1", "agent3"} + + +def test_list_agents_matching_some_tags(server: SyncServer, default_user, agent_with_tags): + agents = server.agent_manager.list_agents_matching_tags( + actor=default_user, + match_all=["primary_agent"], + match_some=["benefit_1", "benefit_2"], + ) + assert len(agents) == 3 # All agents match + assert {a.name for a in agents} == {"agent1", "agent2", "agent3"} + + +def test_list_agents_matching_all_and_some_tags(server: SyncServer, default_user, agent_with_tags): + agents = server.agent_manager.list_agents_matching_tags( + actor=default_user, + match_all=["primary_agent", "benefit_1"], + match_some=["benefit_2", "nonexistent"], + ) + assert len(agents) == 1 # Only agent3 matches + assert agents[0].name == "agent3" + + +def test_list_agents_matching_no_tags(server: SyncServer, default_user, agent_with_tags): + agents = server.agent_manager.list_agents_matching_tags( + actor=default_user, + match_all=["primary_agent", "nonexistent_tag"], + match_some=["benefit_1", "benefit_2"], + ) + assert len(agents) == 0 # No agent should match + + def test_list_agents_by_tags_match_all(server: SyncServer, sarah_agent, charles_agent, default_user): """Test listing agents that have ALL specified tags.""" # Create agents with multiple tags @@ -1073,6 +1154,73 @@ def test_reset_messages_idempotency(server: SyncServer, sarah_agent, default_use assert server.message_manager.size(agent_id=sarah_agent.id, actor=default_user) == 1 +def test_modify_letta_message(server: SyncServer, sarah_agent, default_user): + """ + Test updating a message. + """ + + messages = server.message_manager.list_messages_for_agent(agent_id=sarah_agent.id, actor=default_user) + letta_messages = PydanticMessage.to_letta_messages_from_list(messages=messages) + + system_message = [msg for msg in letta_messages if msg.message_type == "system_message"][0] + assistant_message = [msg for msg in letta_messages if msg.message_type == "assistant_message"][0] + user_message = [msg for msg in letta_messages if msg.message_type == "user_message"][0] + reasoning_message = [msg for msg in letta_messages if msg.message_type == "reasoning_message"][0] + + # user message + update_user_message = UpdateUserMessage(content="Hello, Sarah!") + original_user_message = server.message_manager.get_message_by_id(message_id=user_message.id, actor=default_user) + assert original_user_message.content[0].text != update_user_message.content + server.message_manager.update_message_by_letta_message( + message_id=user_message.id, letta_message_update=update_user_message, actor=default_user + ) + updated_user_message = server.message_manager.get_message_by_id(message_id=user_message.id, actor=default_user) + assert updated_user_message.content[0].text == update_user_message.content + + # system message + update_system_message = UpdateSystemMessage(content="You are a friendly assistant!") + original_system_message = server.message_manager.get_message_by_id(message_id=system_message.id, actor=default_user) + assert original_system_message.content[0].text != update_system_message.content + server.message_manager.update_message_by_letta_message( + message_id=system_message.id, letta_message_update=update_system_message, actor=default_user + ) + updated_system_message = server.message_manager.get_message_by_id(message_id=system_message.id, actor=default_user) + assert updated_system_message.content[0].text == update_system_message.content + + # reasoning message + update_reasoning_message = UpdateReasoningMessage(reasoning="I am thinking") + original_reasoning_message = server.message_manager.get_message_by_id(message_id=reasoning_message.id, actor=default_user) + assert original_reasoning_message.content[0].text != update_reasoning_message.reasoning + server.message_manager.update_message_by_letta_message( + message_id=reasoning_message.id, letta_message_update=update_reasoning_message, actor=default_user + ) + updated_reasoning_message = server.message_manager.get_message_by_id(message_id=reasoning_message.id, actor=default_user) + assert updated_reasoning_message.content[0].text == update_reasoning_message.reasoning + + # assistant message + def parse_send_message(tool_call): + import json + + function_call = tool_call.function + arguments = json.loads(function_call.arguments) + return arguments["message"] + + update_assistant_message = UpdateAssistantMessage(content="I am an agent!") + original_assistant_message = server.message_manager.get_message_by_id(message_id=assistant_message.id, actor=default_user) + print("ORIGINAL", original_assistant_message.tool_calls) + print("MESSAGE", parse_send_message(original_assistant_message.tool_calls[0])) + assert parse_send_message(original_assistant_message.tool_calls[0]) != update_assistant_message.content + server.message_manager.update_message_by_letta_message( + message_id=assistant_message.id, letta_message_update=update_assistant_message, actor=default_user + ) + updated_assistant_message = server.message_manager.get_message_by_id(message_id=assistant_message.id, actor=default_user) + print("UPDATED", updated_assistant_message.tool_calls) + print("MESSAGE", parse_send_message(updated_assistant_message.tool_calls[0])) + assert parse_send_message(updated_assistant_message.tool_calls[0]) == update_assistant_message.content + + # TODO: tool calls/responses + + # ====================================================================================================================== # AgentManager Tests - Blocks Relationship # ====================================================================================================================== @@ -2001,28 +2149,42 @@ def test_update_block(server: SyncServer, default_user): def test_update_block_limit(server: SyncServer, default_user): - block_manager = BlockManager() block = block_manager.create_or_update_block(PydanticBlock(label="persona", value="Original Content"), actor=default_user) limit = len("Updated Content") * 2000 - update_data = BlockUpdate(value="Updated Content" * 2000, description="Updated description", limit=limit) + update_data = BlockUpdate(value="Updated Content" * 2000, description="Updated description") - # Check that a large block fails - try: + # Check that exceeding the block limit raises an exception + with pytest.raises(ValueError): block_manager.update_block(block_id=block.id, block_update=update_data, actor=default_user) - assert False - except Exception: - pass + # Ensure the update works when within limits + update_data = BlockUpdate(value="Updated Content" * 2000, description="Updated description", limit=limit) block_manager.update_block(block_id=block.id, block_update=update_data, actor=default_user) - # Retrieve the updated block + + # Retrieve the updated block and validate the update updated_block = block_manager.get_blocks(actor=default_user, id=block.id)[0] - # Assertions to verify the update + assert updated_block.value == "Updated Content" * 2000 assert updated_block.description == "Updated description" +def test_update_block_limit_does_not_reset(server: SyncServer, default_user): + block_manager = BlockManager() + new_content = "Updated Content" * 2000 + limit = len(new_content) + block = block_manager.create_or_update_block(PydanticBlock(label="persona", value="Original Content", limit=limit), actor=default_user) + + # Ensure the update works + update_data = BlockUpdate(value=new_content) + block_manager.update_block(block_id=block.id, block_update=update_data, actor=default_user) + + # Retrieve the updated block and validate the update + updated_block = block_manager.get_blocks(actor=default_user, id=block.id)[0] + assert updated_block.value == new_content + + def test_delete_block(server: SyncServer, default_user): block_manager = BlockManager() @@ -2075,6 +2237,154 @@ def test_get_agents_for_block(server: SyncServer, sarah_agent, charles_agent, de assert charles_agent.id in agent_state_ids +# ====================================================================================================================== +# Identity Manager Tests +# ====================================================================================================================== + + +def test_create_and_upsert_identity(server: SyncServer, default_user): + identity_manager = IdentityManager() + identity_create = IdentityCreate( + identifier_key="1234", + name="caren", + identity_type=IdentityType.user, + properties=[ + IdentityProperty(key="email", value="caren@letta.com", type=IdentityPropertyType.string), + IdentityProperty(key="age", value=28, type=IdentityPropertyType.number), + ], + ) + + identity = identity_manager.create_identity(identity_create, actor=default_user) + + # Assertions to ensure the created identity matches the expected values + assert identity.identifier_key == identity_create.identifier_key + assert identity.name == identity_create.name + assert identity.identity_type == identity_create.identity_type + assert identity.properties == identity_create.properties + assert identity.agent_ids == [] + assert identity.project_id == None + + with pytest.raises(UniqueConstraintViolationError): + identity_manager.create_identity( + IdentityCreate(identifier_key="1234", name="sarah", identity_type=IdentityType.user), + actor=default_user, + ) + + identity_create.properties = [(IdentityProperty(key="age", value=29, type=IdentityPropertyType.number))] + + identity = identity_manager.upsert_identity(identity_create, actor=default_user) + + identity = identity_manager.get_identity(identity_id=identity.id, actor=default_user) + assert len(identity.properties) == 1 + assert identity.properties[0].key == "age" + assert identity.properties[0].value == 29 + + identity_manager.delete_identity(identity.id, actor=default_user) + + +def test_get_identities(server, default_user): + identity_manager = IdentityManager() + + # Create identities to retrieve later + user = identity_manager.create_identity( + IdentityCreate(name="caren", identifier_key="1234", identity_type=IdentityType.user), actor=default_user + ) + org = identity_manager.create_identity( + IdentityCreate(name="letta", identifier_key="0001", identity_type=IdentityType.org), actor=default_user + ) + + # Retrieve identities by different filters + all_identities = identity_manager.list_identities(actor=default_user) + assert len(all_identities) == 2 + + user_identities = identity_manager.list_identities(actor=default_user, identity_type=IdentityType.user) + assert len(user_identities) == 1 + assert user_identities[0].name == user.name + + org_identities = identity_manager.list_identities(actor=default_user, identity_type=IdentityType.org) + assert len(org_identities) == 1 + assert org_identities[0].name == org.name + + identity_manager.delete_identity(user.id, actor=default_user) + identity_manager.delete_identity(org.id, actor=default_user) + + +def test_update_identity(server: SyncServer, sarah_agent, charles_agent, default_user): + identity = server.identity_manager.create_identity( + IdentityCreate(name="caren", identifier_key="1234", identity_type=IdentityType.user), actor=default_user + ) + + # Update identity fields + update_data = IdentityUpdate( + agent_ids=[sarah_agent.id, charles_agent.id], + properties=[IdentityProperty(key="email", value="caren@letta.com", type=IdentityPropertyType.string)], + ) + server.identity_manager.update_identity(identity_id=identity.id, identity=update_data, actor=default_user) + + # Retrieve the updated identity + updated_identity = server.identity_manager.get_identity(identity_id=identity.id, actor=default_user) + + # Assertions to verify the update + assert updated_identity.agent_ids.sort() == update_data.agent_ids.sort() + assert updated_identity.properties == update_data.properties + + agent_state = server.agent_manager.get_agent_by_id(agent_id=sarah_agent.id, actor=default_user) + assert identity.id in agent_state.identity_ids + agent_state = server.agent_manager.get_agent_by_id(agent_id=charles_agent.id, actor=default_user) + assert identity.id in agent_state.identity_ids + + server.identity_manager.delete_identity(identity.id, actor=default_user) + + +def test_attach_detach_identity_from_agent(server: SyncServer, sarah_agent, default_user): + # Create an identity + identity = server.identity_manager.create_identity( + IdentityCreate(name="caren", identifier_key="1234", identity_type=IdentityType.user), actor=default_user + ) + agent_state = server.agent_manager.update_agent( + agent_id=sarah_agent.id, agent_update=UpdateAgent(identity_ids=[identity.id]), actor=default_user + ) + + # Check that identity has been attached + assert identity.id in agent_state.identity_ids + + # Now attempt to delete the identity + server.identity_manager.delete_identity(identity_id=identity.id, actor=default_user) + + # Verify that the identity was deleted + identities = server.identity_manager.list_identities(actor=default_user) + assert len(identities) == 0 + + # Check that block has been detached too + agent_state = server.agent_manager.get_agent_by_id(agent_id=sarah_agent.id, actor=default_user) + assert not identity.id in agent_state.identity_ids + + +def test_get_agents_for_identities(server: SyncServer, sarah_agent, charles_agent, default_user): + identity = server.identity_manager.create_identity( + IdentityCreate(name="caren", identifier_key="1234", identity_type=IdentityType.user, agent_ids=[sarah_agent.id, charles_agent.id]), + actor=default_user, + ) + + # Get the agents for identity id + agent_states = server.agent_manager.list_agents(identifier_id=identity.id, actor=default_user) + assert len(agent_states) == 2 + + # Check both agents are in the list + agent_state_ids = [a.id for a in agent_states] + assert sarah_agent.id in agent_state_ids + assert charles_agent.id in agent_state_ids + + # Get the agents for identifier key + agent_states = server.agent_manager.list_agents(identifier_keys=[identity.identifier_key], actor=default_user) + assert len(agent_states) == 2 + + # Check both agents are in the list + agent_state_ids = [a.id for a in agent_states] + assert sarah_agent.id in agent_state_ids + assert charles_agent.id in agent_state_ids + + # ====================================================================================================================== # SourceManager Tests - Sources # ====================================================================================================================== @@ -3095,13 +3405,14 @@ def test_get_run_messages(server: SyncServer, default_user: PydanticUser, sarah_ # ====================================================================================================================== -def test_job_usage_stats_add_and_get(server: SyncServer, default_job, default_user): +def test_job_usage_stats_add_and_get(server: SyncServer, sarah_agent, default_job, default_user): """Test adding and retrieving job usage statistics.""" job_manager = server.job_manager step_manager = server.step_manager # Add usage statistics step_manager.log_step( + agent_id=sarah_agent.id, provider_name="openai", model="gpt-4", model_endpoint="https://api.openai.com/v1", @@ -3145,13 +3456,14 @@ def test_job_usage_stats_get_no_stats(server: SyncServer, default_job, default_u assert len(steps) == 0 -def test_job_usage_stats_add_multiple(server: SyncServer, default_job, default_user): +def test_job_usage_stats_add_multiple(server: SyncServer, sarah_agent, default_job, default_user): """Test adding multiple usage statistics entries for a job.""" job_manager = server.job_manager step_manager = server.step_manager # Add first usage statistics entry step_manager.log_step( + agent_id=sarah_agent.id, provider_name="openai", model="gpt-4", model_endpoint="https://api.openai.com/v1", @@ -3167,6 +3479,7 @@ def test_job_usage_stats_add_multiple(server: SyncServer, default_job, default_u # Add second usage statistics entry step_manager.log_step( + agent_id=sarah_agent.id, provider_name="openai", model="gpt-4", model_endpoint="https://api.openai.com/v1", @@ -3193,6 +3506,10 @@ def test_job_usage_stats_add_multiple(server: SyncServer, default_job, default_u steps = job_manager.get_job_steps(job_id=default_job.id, actor=default_user) assert len(steps) == 2 + # get agent steps + steps = step_manager.list_steps(agent_id=sarah_agent.id, actor=default_user) + assert len(steps) == 2 + def test_job_usage_stats_get_nonexistent_job(server: SyncServer, default_user): """Test getting usage statistics for a nonexistent job.""" @@ -3202,12 +3519,13 @@ def test_job_usage_stats_get_nonexistent_job(server: SyncServer, default_user): job_manager.get_job_usage(job_id="nonexistent_job", actor=default_user) -def test_job_usage_stats_add_nonexistent_job(server: SyncServer, default_user): +def test_job_usage_stats_add_nonexistent_job(server: SyncServer, sarah_agent, default_user): """Test adding usage statistics for a nonexistent job.""" step_manager = server.step_manager with pytest.raises(NoResultFound): step_manager.log_step( + agent_id=sarah_agent.id, provider_name="openai", model="gpt-4", model_endpoint="https://api.openai.com/v1",