diff --git a/letta/services/agent_manager.py b/letta/services/agent_manager.py index 1eacb90cf..828456cb2 100644 --- a/letta/services/agent_manager.py +++ b/letta/services/agent_manager.py @@ -1,8 +1,10 @@ from datetime import datetime -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Set, Tuple import numpy as np -from sqlalchemy import Select, and_, func, literal, or_, select, union_all +import sqlalchemy as sa +from sqlalchemy import Select, and_, func, insert, literal, or_, select, union_all +from sqlalchemy.dialects.postgresql import insert as pg_insert from letta.constants import ( BASE_MEMORY_TOOLS, @@ -19,13 +21,17 @@ from letta.log import get_logger from letta.orm import Agent as AgentModel from letta.orm import AgentPassage, AgentsTags from letta.orm import Block as BlockModel +from letta.orm import BlocksAgents from letta.orm import Group as GroupModel +from letta.orm import IdentitiesAgents from letta.orm import Identity as IdentityModel from letta.orm import Source as SourceModel from letta.orm import SourcePassage, SourcesAgents from letta.orm import Tool as ToolModel +from letta.orm import ToolsAgents from letta.orm.enums import ToolType from letta.orm.errors import NoResultFound +from letta.orm.sandbox_config import AgentEnvironmentVariable from letta.orm.sandbox_config import AgentEnvironmentVariable as AgentEnvironmentVariableModel from letta.orm.sqlalchemy_base import AccessType from letta.orm.sqlite_functions import adapt_array @@ -36,16 +42,14 @@ from letta.schemas.block import BlockUpdate from letta.schemas.embedding_config import EmbeddingConfig from letta.schemas.group import Group as PydanticGroup from letta.schemas.group import ManagerType -from letta.schemas.llm_config import LLMConfig from letta.schemas.memory import Memory +from letta.schemas.message import Message from letta.schemas.message import Message as PydanticMessage from letta.schemas.message import MessageCreate, MessageUpdate from letta.schemas.passage import Passage as PydanticPassage from letta.schemas.source import Source as PydanticSource from letta.schemas.tool import Tool as PydanticTool -from letta.schemas.tool_rule import ContinueToolRule as PydanticContinueToolRule -from letta.schemas.tool_rule import TerminalToolRule as PydanticTerminalToolRule -from letta.schemas.tool_rule import ToolRule as PydanticToolRule +from letta.schemas.tool_rule import ContinueToolRule, TerminalToolRule from letta.schemas.user import User as PydanticUser from letta.serialize_schemas import MarshmallowAgentSchema from letta.serialize_schemas.marshmallow_message import SerializedMessageSchema @@ -77,7 +81,6 @@ from letta.utils import enforce_types, united_diff logger = get_logger(__name__) -# Agent Manager Class class AgentManager: """Manager class to handle business logic related to Agents.""" @@ -92,124 +95,206 @@ class AgentManager: self.passage_manager = PassageManager() self.identity_manager = IdentityManager() + @staticmethod + def _resolve_tools(session, names: Set[str], ids: Set[str], org_id: str) -> Tuple[Dict[str, str], Dict[str, str]]: + """ + Bulk‑fetch all ToolModel rows matching either name ∈ names or id ∈ ids + (and scoped to this organization), and return two maps: + name_to_id, id_to_name. + Raises if any requested name or id was not found. + """ + stmt = select(ToolModel.id, ToolModel.name).where( + ToolModel.organization_id == org_id, + or_( + ToolModel.name.in_(names), + ToolModel.id.in_(ids), + ), + ) + rows = session.execute(stmt).all() + name_to_id = {name: tid for tid, name in rows} + id_to_name = {tid: name for tid, name in rows} + + missing_names = names - set(name_to_id.keys()) + missing_ids = ids - set(id_to_name.keys()) + if missing_names: + raise ValueError(f"Tools not found by name: {missing_names}") + if missing_ids: + raise ValueError(f"Tools not found by id: {missing_ids}") + + return name_to_id, id_to_name + + @staticmethod + @trace_method + def _bulk_insert_pivot(session, table, rows: list[dict]): + if not rows: + return + + dialect = session.bind.dialect.name + if dialect == "postgresql": + stmt = pg_insert(table).values(rows).on_conflict_do_nothing() + elif dialect == "sqlite": + stmt = sa.insert(table).values(rows).prefix_with("OR IGNORE") + else: + # fallback: filter out exact-duplicate dicts in Python + seen = set() + filtered = [] + for row in rows: + key = tuple(sorted(row.items())) + if key not in seen: + seen.add(key) + filtered.append(row) + stmt = sa.insert(table).values(filtered) + + session.execute(stmt) + # ====================================================================================================================== # Basic CRUD operations # ====================================================================================================================== @trace_method - @enforce_types - def create_agent( - self, - agent_create: CreateAgent, - actor: PydanticUser, - ) -> PydanticAgentState: - system = derive_system_message( - agent_type=agent_create.agent_type, - enable_sleeptime=agent_create.enable_sleeptime, - system=agent_create.system, - ) - + def create_agent(self, agent_create: CreateAgent, actor: PydanticUser) -> PydanticAgentState: + # validate required configs if not agent_create.llm_config or not agent_create.embedding_config: raise ValueError("llm_config and embedding_config are required") - # create blocks (note: cannot be linked into the agent_id is created) - block_ids = list(agent_create.block_ids or []) # Create a local copy to avoid modifying the original + # blocks + block_ids = list(agent_create.block_ids or []) if agent_create.memory_blocks: - for create_block in agent_create.memory_blocks: - block = self.block_manager.create_or_update_block(PydanticBlock(**create_block.model_dump(to_orm=True)), actor=actor) - block_ids.append(block.id) - - # add passed in `tools` - tool_names = agent_create.tools or [] - - # add base tools - if agent_create.include_base_tools: - if agent_create.agent_type == AgentType.sleeptime_agent: - tool_names.extend(BASE_SLEEPTIME_TOOLS) - else: - if agent_create.enable_sleeptime: - tool_names.extend(BASE_SLEEPTIME_CHAT_TOOLS) - else: - tool_names.extend(BASE_TOOLS + BASE_MEMORY_TOOLS) - if agent_create.include_multi_agent_tools: - tool_names.extend(MULTI_AGENT_TOOLS) - - # remove duplicates - tool_names = list(set(tool_names)) - - # convert tool names to ids - tool_ids = [] - for tool_name in tool_names: - tool = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor) - if not tool: - raise ValueError(f"Tool {tool_name} not found") - tool_ids.append(tool.id) - - # add passed in `tool_ids` - for tool_id in agent_create.tool_ids or []: - if tool_id not in tool_ids: - tool = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=actor) - if tool: - tool_ids.append(tool.id) - tool_names.append(tool.name) - else: - raise ValueError(f"Tool {tool_id} not found") - - # add default tool rules - tool_rules = agent_create.tool_rules or [] - if agent_create.include_base_tool_rules: - # apply default tool rules - for tool_name in tool_names: - if tool_name == "send_message" or tool_name == "send_message_to_agent_async" or tool_name == "memory_finish_edits": - tool_rules.append(PydanticTerminalToolRule(tool_name=tool_name)) - elif tool_name in BASE_TOOLS + BASE_MEMORY_TOOLS + BASE_SLEEPTIME_TOOLS: - tool_rules.append(PydanticContinueToolRule(tool_name=tool_name)) - - # if custom rules, check tool rules are valid - if agent_create.tool_rules: - check_supports_structured_output(model=agent_create.llm_config.model, tool_rules=agent_create.tool_rules) - - # Create the agent - agent_state = self._create_agent( - name=agent_create.name, - system=system, - agent_type=agent_create.agent_type, - llm_config=agent_create.llm_config, - embedding_config=agent_create.embedding_config, - block_ids=block_ids, - tool_ids=tool_ids, - source_ids=agent_create.source_ids or [], - tags=agent_create.tags or [], - identity_ids=agent_create.identity_ids or [], - description=agent_create.description, - metadata=agent_create.metadata, - tool_rules=tool_rules, - actor=actor, - project_id=agent_create.project_id, - template_id=agent_create.template_id, - base_template_id=agent_create.base_template_id, - message_buffer_autoclear=agent_create.message_buffer_autoclear, - enable_sleeptime=agent_create.enable_sleeptime, - ) - - # If there are provided environment variables, add them in - if agent_create.tool_exec_environment_variables: - agent_state = self._set_environment_variables( - agent_id=agent_state.id, - env_vars=agent_create.tool_exec_environment_variables, + pydantic_blocks = [PydanticBlock(**b.model_dump(to_orm=True)) for b in agent_create.memory_blocks] + created_blocks = self.block_manager.batch_create_blocks( + pydantic_blocks, actor=actor, ) + block_ids.extend([blk.id for blk in created_blocks]) - return self.append_initial_message_sequence_to_in_context_messages(actor, agent_state, agent_create.initial_message_sequence) + # tools + tool_names = set(agent_create.tools or []) + if agent_create.include_base_tools: + if agent_create.agent_type == AgentType.sleeptime_agent: + tool_names |= set(BASE_SLEEPTIME_TOOLS) + elif agent_create.enable_sleeptime: + tool_names |= set(BASE_SLEEPTIME_CHAT_TOOLS) + else: + tool_names |= set(BASE_TOOLS + BASE_MEMORY_TOOLS) + if agent_create.include_multi_agent_tools: + tool_names |= set(MULTI_AGENT_TOOLS) + + supplied_ids = set(agent_create.tool_ids or []) + + source_ids = agent_create.source_ids or [] + identity_ids = agent_create.identity_ids or [] + tag_values = agent_create.tags or [] + + with self.session_maker() as session: + with session.begin(): + name_to_id, id_to_name = self._resolve_tools( + session, + tool_names, + supplied_ids, + actor.organization_id, + ) + + tool_ids = set(name_to_id.values()) | set(id_to_name.keys()) + tool_names = set(name_to_id.keys()) # now canonical + + tool_rules = list(agent_create.tool_rules or []) + if agent_create.include_base_tool_rules: + for tn in tool_names: + if tn in {"send_message", "send_message_to_agent_async", "memory_finish_edits"}: + tool_rules.append(TerminalToolRule(tool_name=tn)) + elif tn in (BASE_TOOLS + BASE_MEMORY_TOOLS + BASE_SLEEPTIME_TOOLS): + tool_rules.append(ContinueToolRule(tool_name=tn)) + + if tool_rules: + check_supports_structured_output(model=agent_create.llm_config.model, tool_rules=tool_rules) + + new_agent = AgentModel( + name=agent_create.name, + system=derive_system_message( + agent_type=agent_create.agent_type, + enable_sleeptime=agent_create.enable_sleeptime, + system=agent_create.system, + ), + agent_type=agent_create.agent_type, + llm_config=agent_create.llm_config, + embedding_config=agent_create.embedding_config, + organization_id=actor.organization_id, + description=agent_create.description, + metadata_=agent_create.metadata, + tool_rules=tool_rules, + project_id=agent_create.project_id, + template_id=agent_create.template_id, + base_template_id=agent_create.base_template_id, + message_buffer_autoclear=agent_create.message_buffer_autoclear, + enable_sleeptime=agent_create.enable_sleeptime, + created_by_id=actor.id, + last_updated_by_id=actor.id, + ) + session.add(new_agent) + session.flush() + aid = new_agent.id + + self._bulk_insert_pivot( + session, + ToolsAgents.__table__, + [{"agent_id": aid, "tool_id": tid} for tid in tool_ids], + ) + + if block_ids: + rows = [ + {"agent_id": aid, "block_id": bid, "block_label": lbl} + for bid, lbl in session.execute(select(BlockModel.id, BlockModel.label).where(BlockModel.id.in_(block_ids))).all() + ] + self._bulk_insert_pivot(session, BlocksAgents.__table__, rows) + + self._bulk_insert_pivot( + session, + SourcesAgents.__table__, + [{"agent_id": aid, "source_id": sid} for sid in source_ids], + ) + self._bulk_insert_pivot( + session, + AgentsTags.__table__, + [{"agent_id": aid, "tag": tag} for tag in tag_values], + ) + self._bulk_insert_pivot( + session, + IdentitiesAgents.__table__, + [{"agent_id": aid, "identity_id": iid} for iid in identity_ids], + ) + + if agent_create.tool_exec_environment_variables: + env_rows = [ + { + "agent_id": aid, + "key": key, + "value": val, + "organization_id": actor.organization_id, + } + for key, val in agent_create.tool_exec_environment_variables.items() + ] + session.execute(insert(AgentEnvironmentVariable).values(env_rows)) + + # initial message sequence + init_messages = self._generate_initial_message_sequence( + actor, + agent_state=new_agent.to_pydantic(include_relationships={"memory"}), + supplied_initial_message_sequence=agent_create.initial_message_sequence, + ) + new_agent.message_ids = [msg.id for msg in init_messages] + + session.refresh(new_agent) + + self.message_manager.create_many_messages(pydantic_msgs=init_messages, actor=actor) + return new_agent.to_pydantic() @enforce_types - def append_initial_message_sequence_to_in_context_messages( - self, actor: PydanticUser, agent_state: PydanticAgentState, initial_message_sequence: Optional[List[MessageCreate]] = None - ) -> PydanticAgentState: + def _generate_initial_message_sequence( + self, actor: PydanticUser, agent_state: PydanticAgentState, supplied_initial_message_sequence: Optional[List[MessageCreate]] = None + ) -> List[Message]: init_messages = initialize_message_sequence( agent_state=agent_state, memory_edit_timestamp=get_utc_time(), include_initial_boot_message=True ) - - if initial_message_sequence is not None: + if supplied_initial_message_sequence is not None: # We always need the system prompt up front system_message_obj = PydanticMessage.dict_to_message( agent_id=agent_state.id, @@ -219,7 +304,7 @@ class AgentManager: # Don't use anything else in the pregen sequence, instead use the provided sequence init_messages = [system_message_obj] init_messages.extend( - package_initial_message_sequence(agent_state.id, initial_message_sequence, agent_state.llm_config.model, actor) + package_initial_message_sequence(agent_state.id, supplied_initial_message_sequence, agent_state.llm_config.model, actor) ) else: init_messages = [ @@ -227,63 +312,14 @@ class AgentManager: for msg in init_messages ] - return self.append_to_in_context_messages(init_messages, agent_id=agent_state.id, actor=actor) + return init_messages @enforce_types - def _create_agent( - self, - actor: PydanticUser, - name: str, - system: str, - agent_type: AgentType, - llm_config: LLMConfig, - embedding_config: EmbeddingConfig, - block_ids: List[str], - tool_ids: List[str], - source_ids: List[str], - tags: List[str], - identity_ids: List[str], - description: Optional[str] = None, - metadata: Optional[Dict] = None, - tool_rules: Optional[List[PydanticToolRule]] = None, - project_id: Optional[str] = None, - template_id: Optional[str] = None, - base_template_id: Optional[str] = None, - message_buffer_autoclear: bool = False, - enable_sleeptime: Optional[bool] = None, + def append_initial_message_sequence_to_in_context_messages( + self, actor: PydanticUser, agent_state: PydanticAgentState, initial_message_sequence: Optional[List[MessageCreate]] = None ) -> PydanticAgentState: - """Create a new agent.""" - with self.session_maker() as session: - # Prepare the agent data - data = { - "name": name, - "system": system, - "agent_type": agent_type, - "llm_config": llm_config, - "embedding_config": embedding_config, - "organization_id": actor.organization_id, - "description": description, - "metadata_": metadata, - "tool_rules": tool_rules, - "project_id": project_id, - "template_id": template_id, - "base_template_id": base_template_id, - "message_buffer_autoclear": message_buffer_autoclear, - "enable_sleeptime": enable_sleeptime, - } - - # Create the new agent using SqlalchemyBase.create - new_agent = AgentModel(**data) - _process_relationship(session, new_agent, "tools", ToolModel, tool_ids, replace=True) - _process_relationship(session, new_agent, "sources", SourceModel, source_ids, replace=True) - _process_relationship(session, new_agent, "core_memory", BlockModel, block_ids, replace=True) - _process_tags(new_agent, tags, replace=True) - _process_relationship(session, new_agent, "identities", IdentityModel, identity_ids, replace=True) - - new_agent.create(session, actor=actor) - - # Convert to PydanticAgentState and return - return new_agent.to_pydantic() + init_messages = self._generate_initial_message_sequence(actor, agent_state, initial_message_sequence) + return self.append_to_in_context_messages(init_messages, agent_id=agent_state.id, actor=actor) @enforce_types def update_agent(self, agent_id: str, agent_update: UpdateAgent, actor: PydanticUser) -> PydanticAgentState: diff --git a/letta/services/block_manager.py b/letta/services/block_manager.py index 96b0a35b8..40e025196 100644 --- a/letta/services/block_manager.py +++ b/letta/services/block_manager.py @@ -37,6 +37,29 @@ class BlockManager: block.create(session, actor=actor) return block.to_pydantic() + @enforce_types + def batch_create_blocks(self, blocks: List[PydanticBlock], actor: PydanticUser) -> List[PydanticBlock]: + """ + Batch-create multiple Blocks in one transaction for better performance. + Args: + blocks: List of PydanticBlock schemas to create + actor: The user performing the operation + Returns: + List of created PydanticBlock instances (with IDs, timestamps, etc.) + """ + if not blocks: + return [] + + with self.session_maker() as session: + block_models = [ + BlockModel(**block.model_dump(to_orm=True, exclude_none=True), organization_id=actor.organization_id) for block in blocks + ] + + created_models = BlockModel.batch_create(items=block_models, db_session=session, actor=actor) + + # Convert back to Pydantic + return [m.to_pydantic() for m in created_models] + @enforce_types def update_block(self, block_id: str, block_update: BlockUpdate, actor: PydanticUser) -> PydanticBlock: """Update a block by its ID with the given BlockUpdate object.""" diff --git a/letta/services/helpers/agent_manager_helper.py b/letta/services/helpers/agent_manager_helper.py index 1667fb08e..030e54ccf 100644 --- a/letta/services/helpers/agent_manager_helper.py +++ b/letta/services/helpers/agent_manager_helper.py @@ -21,9 +21,11 @@ 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 +from letta.tracing import trace_method # Static methods +@trace_method def _process_relationship( session, agent: AgentModel, relationship_name: str, model_class, item_ids: List[str], allow_partial=False, replace=True ): diff --git a/performance_tests/test_agent_mass_creation.py b/performance_tests/test_agent_mass_creation.py index 4c7e4345e..f9dc57db0 100644 --- a/performance_tests/test_agent_mass_creation.py +++ b/performance_tests/test_agent_mass_creation.py @@ -5,6 +5,8 @@ import time import uuid from concurrent.futures import ThreadPoolExecutor, as_completed +import matplotlib.pyplot as plt +import pandas as pd import pytest from dotenv import load_dotenv from letta_client import Letta @@ -131,10 +133,9 @@ def agent_state(client, roll_dice_tool, weather_tool, rethink_tool): # --- Load Test --- # -def create_agents_for_user(client, roll_dice_tool, rethink_tool, user_index: int) -> float: - """Create agents and return E2E latency in seconds.""" - start_time = time.time() - +def create_agents_for_user(client, roll_dice_tool, rethink_tool, user_index: int) -> tuple: + """Create agents and return E2E latencies in seconds along with user index.""" + # Setup blocks first num_blocks = 10 blocks = [] for i in range(num_blocks): @@ -148,8 +149,12 @@ def create_agents_for_user(client, roll_dice_tool, rethink_tool, user_index: int blocks.append(block) block_ids = [b.id for b in blocks] + # Now create agents and track individual latencies + agent_latencies = [] num_agents_per_user = 100 for i in range(num_agents_per_user): + start_time = time.time() + client.agents.create( name=f"user{user_index}_agent_{str(uuid.uuid4())[5:]}", tool_ids=[roll_dice_tool.id, rethink_tool.id], @@ -163,16 +168,94 @@ def create_agents_for_user(client, roll_dice_tool, rethink_tool, user_index: int block_ids=block_ids, ) - end_time = time.time() - return end_time - start_time + end_time = time.time() + latency = end_time - start_time + agent_latencies.append({"user_index": user_index, "agent_index": i, "latency": latency}) + + return user_index, agent_latencies + + +def plot_agent_creation_latencies(latency_data): + """ + Plot the distribution of agent creation latencies. + + Args: + latency_data: List of dictionaries with latency information + """ + # Convert to DataFrame for easier analysis + df = pd.DataFrame(latency_data) + + # Overall latency distribution + plt.figure(figsize=(12, 10)) + + # Plot 1: Overall latency histogram + plt.subplot(2, 2, 1) + plt.hist(df["latency"], bins=30, alpha=0.7, color="blue") + plt.title(f"Agent Creation Latency Distribution (n={len(df)})") + plt.xlabel("Latency (seconds)") + plt.ylabel("Frequency") + plt.grid(True, alpha=0.3) + + # Plot 2: Latency by user (boxplot) + plt.subplot(2, 2, 2) + user_groups = df.groupby("user_index") + plt.boxplot([group["latency"] for _, group in user_groups]) + plt.title("Latency Distribution by User") + plt.xlabel("User Index") + plt.ylabel("Latency (seconds)") + plt.xticks(range(1, len(user_groups) + 1), sorted(df["user_index"].unique())) + plt.grid(True, alpha=0.3) + + # Plot 3: Time series of latencies + plt.subplot(2, 1, 2) + for user_idx in sorted(df["user_index"].unique()): + user_data = df[df["user_index"] == user_idx] + plt.plot(user_data["agent_index"], user_data["latency"], marker=".", linestyle="-", alpha=0.7, label=f"User {user_idx}") + + plt.title("Agent Creation Latency Over Time") + plt.xlabel("Agent Creation Sequence") + plt.ylabel("Latency (seconds)") + plt.legend(loc="upper right") + plt.grid(True, alpha=0.3) + + # Add statistics as text + stats_text = ( + f"Mean: {df['latency'].mean():.2f}s\n" + f"Median: {df['latency'].median():.2f}s\n" + f"Min: {df['latency'].min():.2f}s\n" + f"Max: {df['latency'].max():.2f}s\n" + f"Std Dev: {df['latency'].std():.2f}s" + ) + plt.figtext(0.02, 0.02, stats_text, fontsize=10, bbox=dict(facecolor="white", alpha=0.8)) + + plt.tight_layout() + + # Save the plot + plot_file = f"agent_creation_latency_plot_{time.strftime('%Y%m%d_%H%M%S')}.png" + plt.savefig(plot_file) + plt.close() + + print(f"Latency plot saved to {plot_file}") + + # Return statistics for reporting + return { + "mean": df["latency"].mean(), + "median": df["latency"].median(), + "min": df["latency"].min(), + "max": df["latency"].max(), + "std": df["latency"].std(), + "count": len(df), + "plot_file": plot_file, + } @pytest.mark.slow def test_parallel_create_many_agents(client, roll_dice_tool, rethink_tool): - num_users = 10 + num_users = 7 max_workers = min(num_users, 20) - latencies = [] + # To collect all latency data across users + all_latency_data = [] with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = { @@ -182,15 +265,30 @@ def test_parallel_create_many_agents(client, roll_dice_tool, rethink_tool): with tqdm(total=num_users, desc="Creating agents") as pbar: for future in as_completed(futures): - user_idx = futures[future] try: - latency = future.result() - latencies.append(latency) - tqdm.write(f"[User {user_idx}] Agent creation latency: {latency:.2f} seconds") + user_idx, user_latencies = future.result() + all_latency_data.extend(user_latencies) + + # Calculate and display per-user statistics + latencies = [data["latency"] for data in user_latencies] + avg_latency = sum(latencies) / len(latencies) + tqdm.write(f"[User {user_idx}] Completed {len(latencies)} agents") + tqdm.write(f"[User {user_idx}] Avg: {avg_latency:.2f}s, Min: {min(latencies):.2f}s, Max: {max(latencies):.2f}s") except Exception as e: + user_idx = futures[future] tqdm.write(f"[User {user_idx}] Error during agent creation: {str(e)}") pbar.update(1) - if latencies: - avg_latency = sum(latencies) / len(latencies) - print(f"Average agent creation latency per user: {avg_latency:.2f} seconds") + if all_latency_data: + # Plot all collected latency data + stats = plot_agent_creation_latencies(all_latency_data) + + print("\n===== Agent Creation Latency Statistics =====") + print(f"Total agents created: {stats['count']}") + print(f"Mean latency: {stats['mean']:.2f} seconds") + print(f"Median latency: {stats['median']:.2f} seconds") + print(f"Min latency: {stats['min']:.2f} seconds") + print(f"Max latency: {stats['max']:.2f} seconds") + print(f"Standard deviation: {stats['std']:.2f} seconds") + print(f"Latency plot saved to: {stats['plot_file']}") + print("============================================") diff --git a/poetry.lock b/poetry.lock index e94f5c25d..5a412260f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -963,6 +963,82 @@ files = [ test = ["PyYAML", "mock", "pytest"] yaml = ["PyYAML"] +[[package]] +name = "contourpy" +version = "1.3.2" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.10" +files = [ + {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, + {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631"}, + {file = "contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f"}, + {file = "contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2"}, + {file = "contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0"}, + {file = "contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a"}, + {file = "contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445"}, + {file = "contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7"}, + {file = "contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83"}, + {file = "contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd"}, + {file = "contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f"}, + {file = "contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878"}, + {file = "contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2"}, + {file = "contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe"}, + {file = "contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441"}, + {file = "contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e"}, + {file = "contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912"}, + {file = "contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73"}, + {file = "contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb"}, + {file = "contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841"}, + {file = "contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422"}, + {file = "contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef"}, + {file = "contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f"}, + {file = "contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9"}, + {file = "contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f"}, + {file = "contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b"}, + {file = "contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52"}, + {file = "contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd"}, + {file = "contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1"}, + {file = "contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5"}, + {file = "contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.15.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + [[package]] name = "cryptography" version = "44.0.2" @@ -1020,6 +1096,21 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "dataclasses-json" version = "0.6.7" @@ -1438,6 +1529,79 @@ files = [ Flask = ">=1.0.4" Werkzeug = ">=1.0.1" +[[package]] +name = "fonttools" +version = "4.57.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.57.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:babe8d1eb059a53e560e7bf29f8e8f4accc8b6cfb9b5fd10e485bde77e71ef41"}, + {file = "fonttools-4.57.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81aa97669cd726349eb7bd43ca540cf418b279ee3caba5e2e295fb4e8f841c02"}, + {file = "fonttools-4.57.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0e9618630edd1910ad4f07f60d77c184b2f572c8ee43305ea3265675cbbfe7e"}, + {file = "fonttools-4.57.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34687a5d21f1d688d7d8d416cb4c5b9c87fca8a1797ec0d74b9fdebfa55c09ab"}, + {file = "fonttools-4.57.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69ab81b66ebaa8d430ba56c7a5f9abe0183afefd3a2d6e483060343398b13fb1"}, + {file = "fonttools-4.57.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d639397de852f2ccfb3134b152c741406752640a266d9c1365b0f23d7b88077f"}, + {file = "fonttools-4.57.0-cp310-cp310-win32.whl", hash = "sha256:cc066cb98b912f525ae901a24cd381a656f024f76203bc85f78fcc9e66ae5aec"}, + {file = "fonttools-4.57.0-cp310-cp310-win_amd64.whl", hash = "sha256:7a64edd3ff6a7f711a15bd70b4458611fb240176ec11ad8845ccbab4fe6745db"}, + {file = "fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4"}, + {file = "fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8"}, + {file = "fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683"}, + {file = "fonttools-4.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746"}, + {file = "fonttools-4.57.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344"}, + {file = "fonttools-4.57.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f"}, + {file = "fonttools-4.57.0-cp311-cp311-win32.whl", hash = "sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36"}, + {file = "fonttools-4.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d"}, + {file = "fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31"}, + {file = "fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92"}, + {file = "fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888"}, + {file = "fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6"}, + {file = "fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98"}, + {file = "fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8"}, + {file = "fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac"}, + {file = "fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9"}, + {file = "fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef"}, + {file = "fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c"}, + {file = "fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72"}, + {file = "fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817"}, + {file = "fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9"}, + {file = "fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13"}, + {file = "fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199"}, + {file = "fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3"}, + {file = "fonttools-4.57.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d57b4e23ebbe985125d3f0cabbf286efa191ab60bbadb9326091050d88e8213"}, + {file = "fonttools-4.57.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:579ba873d7f2a96f78b2e11028f7472146ae181cae0e4d814a37a09e93d5c5cc"}, + {file = "fonttools-4.57.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3e1ec10c29bae0ea826b61f265ec5c858c5ba2ce2e69a71a62f285cf8e4595"}, + {file = "fonttools-4.57.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1968f2a2003c97c4ce6308dc2498d5fd4364ad309900930aa5a503c9851aec8"}, + {file = "fonttools-4.57.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:aff40f8ac6763d05c2c8f6d240c6dac4bb92640a86d9b0c3f3fff4404f34095c"}, + {file = "fonttools-4.57.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d07f1b64008e39fceae7aa99e38df8385d7d24a474a8c9872645c4397b674481"}, + {file = "fonttools-4.57.0-cp38-cp38-win32.whl", hash = "sha256:51d8482e96b28fb28aa8e50b5706f3cee06de85cbe2dce80dbd1917ae22ec5a6"}, + {file = "fonttools-4.57.0-cp38-cp38-win_amd64.whl", hash = "sha256:03290e818782e7edb159474144fca11e36a8ed6663d1fcbd5268eb550594fd8e"}, + {file = "fonttools-4.57.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7339e6a3283e4b0ade99cade51e97cde3d54cd6d1c3744459e886b66d630c8b3"}, + {file = "fonttools-4.57.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:05efceb2cb5f6ec92a4180fcb7a64aa8d3385fd49cfbbe459350229d1974f0b1"}, + {file = "fonttools-4.57.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a97bb05eb24637714a04dee85bdf0ad1941df64fe3b802ee4ac1c284a5f97b7c"}, + {file = "fonttools-4.57.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:541cb48191a19ceb1a2a4b90c1fcebd22a1ff7491010d3cf840dd3a68aebd654"}, + {file = "fonttools-4.57.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cdef9a056c222d0479a1fdb721430f9efd68268014c54e8166133d2643cb05d9"}, + {file = "fonttools-4.57.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3cf97236b192a50a4bf200dc5ba405aa78d4f537a2c6e4c624bb60466d5b03bd"}, + {file = "fonttools-4.57.0-cp39-cp39-win32.whl", hash = "sha256:e952c684274a7714b3160f57ec1d78309f955c6335c04433f07d36c5eb27b1f9"}, + {file = "fonttools-4.57.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2a722c0e4bfd9966a11ff55c895c817158fcce1b2b6700205a376403b546ad9"}, + {file = "fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f"}, + {file = "fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + [[package]] name = "frozenlist" version = "1.5.0" @@ -2608,6 +2772,95 @@ traitlets = ">=5.3" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] +[[package]] +name = "kiwisolver" +version = "1.4.8" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +files = [ + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b"}, + {file = "kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e"}, +] + [[package]] name = "langchain" version = "0.3.23" @@ -3242,6 +3495,63 @@ dev = ["marshmallow-sqlalchemy[tests]", "pre-commit (>=3.5,<5.0)", "tox"] docs = ["furo (==2024.8.6)", "sphinx (==8.2.3)", "sphinx-copybutton (==0.5.2)", "sphinx-design (==0.6.1)", "sphinx-issues (==5.0.0)", "sphinxext-opengraph (==0.10.0)"] tests = ["pytest (<9)", "pytest-lazy-fixtures"] +[[package]] +name = "matplotlib" +version = "3.10.1" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +files = [ + {file = "matplotlib-3.10.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ff2ae14910be903f4a24afdbb6d7d3a6c44da210fc7d42790b87aeac92238a16"}, + {file = "matplotlib-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0721a3fd3d5756ed593220a8b86808a36c5031fce489adb5b31ee6dbb47dd5b2"}, + {file = "matplotlib-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0673b4b8f131890eb3a1ad058d6e065fb3c6e71f160089b65f8515373394698"}, + {file = "matplotlib-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e875b95ac59a7908978fe307ecdbdd9a26af7fa0f33f474a27fcf8c99f64a19"}, + {file = "matplotlib-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2589659ea30726284c6c91037216f64a506a9822f8e50592d48ac16a2f29e044"}, + {file = "matplotlib-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a97ff127f295817bc34517255c9db6e71de8eddaab7f837b7d341dee9f2f587f"}, + {file = "matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401"}, + {file = "matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe"}, + {file = "matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd"}, + {file = "matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c"}, + {file = "matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7"}, + {file = "matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a"}, + {file = "matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107"}, + {file = "matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be"}, + {file = "matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6"}, + {file = "matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d"}, + {file = "matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea"}, + {file = "matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c"}, + {file = "matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b"}, + {file = "matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1"}, + {file = "matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3"}, + {file = "matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6"}, + {file = "matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b"}, + {file = "matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473"}, + {file = "matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01"}, + {file = "matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb"}, + {file = "matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972"}, + {file = "matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3"}, + {file = "matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f"}, + {file = "matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9"}, + {file = "matplotlib-3.10.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:648406f1899f9a818cef8c0231b44dcfc4ff36f167101c3fd1c9151f24220fdc"}, + {file = "matplotlib-3.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:02582304e352f40520727984a5a18f37e8187861f954fea9be7ef06569cf85b4"}, + {file = "matplotlib-3.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3809916157ba871bcdd33d3493acd7fe3037db5daa917ca6e77975a94cef779"}, + {file = "matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "matplotlib-inline" version = "0.1.7" @@ -4793,6 +5103,20 @@ cffi = ">=1.4.1" docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] +[[package]] +name = "pyparsing" +version = "3.2.3" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, + {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pypdf" version = "5.4.0" @@ -6846,4 +7170,4 @@ tests = ["wikipedia"] [metadata] lock-version = "2.0" python-versions = "<3.14,>=3.10" -content-hash = "16f6a0c089d3eeca4107a9191201138340570cd40d52ca21a827ac0189fbf15d" +content-hash = "5fd927f0519033fb00c3769ab81d682019e532697a374457c50faf4f75bdca02" diff --git a/pyproject.toml b/pyproject.toml index 0dd901784..2fe4292c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ mcp = "^1.3.0" firecrawl-py = "^1.15.0" apscheduler = "^3.11.0" aiomultiprocess = "^0.9.1" +matplotlib = "^3.10.1" [tool.poetry.extras] diff --git a/tests/test_managers.py b/tests/test_managers.py index 26327c4dc..3a9de0691 100644 --- a/tests/test_managers.py +++ b/tests/test_managers.py @@ -435,7 +435,7 @@ def sarah_agent(server: SyncServer, default_user, default_organization): agent_create=CreateAgent( name="sarah_agent", memory_blocks=[], - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), include_base_tools=False, ), @@ -451,7 +451,7 @@ def charles_agent(server: SyncServer, default_user, default_organization): agent_create=CreateAgent( name="charles_agent", memory_blocks=[CreateBlock(label="human", value="Charles"), CreateBlock(label="persona", value="I am a helpful assistant")], - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), include_base_tools=False, ), @@ -466,7 +466,7 @@ def comprehensive_test_agent_fixture(server: SyncServer, default_user, print_too create_agent_request = CreateAgent( system="test system", memory_blocks=memory_blocks, - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), block_ids=[default_block.id], tool_ids=[print_tool.id], @@ -586,7 +586,7 @@ def agent_with_tags(server: SyncServer, default_user): @pytest.fixture def dummy_llm_config() -> LLMConfig: - return LLMConfig.default_config("gpt-4") + return LLMConfig.default_config("gpt-4o-mini") @pytest.fixture @@ -655,7 +655,7 @@ def test_create_agent_passed_in_initial_messages(server: SyncServer, default_use create_agent_request = CreateAgent( system="test system", memory_blocks=memory_blocks, - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), block_ids=[default_block.id], tags=["a", "b"], @@ -669,6 +669,7 @@ def test_create_agent_passed_in_initial_messages(server: SyncServer, default_use ) assert server.message_manager.size(agent_id=agent_state.id, actor=default_user) == 2 init_messages = server.agent_manager.get_in_context_messages(agent_id=agent_state.id, actor=default_user) + # Check that the system appears in the first initial message assert create_agent_request.system in init_messages[0].content[0].text assert create_agent_request.memory_blocks[0].value in init_messages[0].content[0].text @@ -682,7 +683,7 @@ def test_create_agent_default_initial_message(server: SyncServer, default_user, create_agent_request = CreateAgent( system="test system", memory_blocks=memory_blocks, - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), block_ids=[default_block.id], tags=["a", "b"], @@ -710,7 +711,7 @@ def test_create_agent_with_json_in_system_message(server: SyncServer, default_us ) create_agent_request = CreateAgent( system=system_prompt, - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), block_ids=[default_block.id], tags=["a", "b"], @@ -858,7 +859,7 @@ def test_list_agents_ascending(server: SyncServer, default_user): agent1 = server.agent_manager.create_agent( agent_create=CreateAgent( name="agent_oldest", - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), memory_blocks=[], include_base_tools=False, @@ -872,7 +873,7 @@ def test_list_agents_ascending(server: SyncServer, default_user): agent2 = server.agent_manager.create_agent( agent_create=CreateAgent( name="agent_newest", - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), memory_blocks=[], include_base_tools=False, @@ -890,7 +891,7 @@ def test_list_agents_descending(server: SyncServer, default_user): agent1 = server.agent_manager.create_agent( agent_create=CreateAgent( name="agent_oldest", - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), memory_blocks=[], include_base_tools=False, @@ -904,7 +905,7 @@ def test_list_agents_descending(server: SyncServer, default_user): agent2 = server.agent_manager.create_agent( agent_create=CreateAgent( name="agent_newest", - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), memory_blocks=[], include_base_tools=False, @@ -927,7 +928,7 @@ def test_list_agents_ordering_and_pagination(server: SyncServer, default_user): agent_create=CreateAgent( name=name, memory_blocks=[], - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), include_base_tools=False, ), @@ -1288,7 +1289,7 @@ def test_list_agents_by_tags_pagination(server: SyncServer, default_user, defaul agent_create=CreateAgent( name="agent1", tags=["pagination_test", "tag1"], - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), memory_blocks=[], include_base_tools=False, @@ -1304,7 +1305,7 @@ def test_list_agents_by_tags_pagination(server: SyncServer, default_user, defaul agent_create=CreateAgent( name="agent2", tags=["pagination_test", "tag2"], - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), memory_blocks=[], include_base_tools=False, @@ -1346,7 +1347,7 @@ def test_list_agents_query_text_pagination(server: SyncServer, default_user, def name="Search Agent One", memory_blocks=[], description="This is a search agent for testing", - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), include_base_tools=False, ), @@ -1358,7 +1359,7 @@ def test_list_agents_query_text_pagination(server: SyncServer, default_user, def name="Search Agent Two", memory_blocks=[], description="Another search agent for testing", - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), include_base_tools=False, ), @@ -1370,7 +1371,7 @@ def test_list_agents_query_text_pagination(server: SyncServer, default_user, def name="Different Agent", memory_blocks=[], description="This is a different agent", - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), include_base_tools=False, ), @@ -1700,7 +1701,7 @@ def test_refresh_memory(server: SyncServer, default_user): agent = server.agent_manager.create_agent( CreateAgent( name="test", - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), include_base_tools=False, ), @@ -2706,6 +2707,36 @@ def test_get_agents_for_block(server: SyncServer, sarah_agent, charles_agent, de assert charles_agent.id in agent_state_ids +def test_batch_create_multiple_blocks(server: SyncServer, default_user): + block_manager = BlockManager() + num_blocks = 10 + + # Prepare distinct blocks + blocks_to_create = [PydanticBlock(label=f"batch_label_{i}", value=f"batch_value_{i}") for i in range(num_blocks)] + + # Create the blocks + created_blocks = block_manager.batch_create_blocks(blocks_to_create, actor=default_user) + assert len(created_blocks) == num_blocks + + # Map created blocks by label for lookup + created_by_label = {blk.label: blk for blk in created_blocks} + + # Assert all blocks were created correctly + for i in range(num_blocks): + label = f"batch_label_{i}" + value = f"batch_value_{i}" + assert label in created_by_label, f"Missing label: {label}" + blk = created_by_label[label] + assert blk.value == value + assert blk.organization_id == default_user.organization_id + assert blk.id is not None + + # Confirm all created blocks exist in the full list from get_blocks + all_labels = {blk.label for blk in block_manager.get_blocks(actor=default_user)} + expected_labels = {f"batch_label_{i}" for i in range(num_blocks)} + assert expected_labels.issubset(all_labels) + + # ====================================================================================================================== # Block Manager Tests - Checkpointing # ====================================================================================================================== @@ -3401,7 +3432,7 @@ def test_get_set_agents_for_identities(server: SyncServer, sarah_agent, charles_ agent_with_identity = server.create_agent( CreateAgent( memory_blocks=[], - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), identity_ids=[identity.id], include_base_tools=False, @@ -3411,7 +3442,7 @@ def test_get_set_agents_for_identities(server: SyncServer, sarah_agent, charles_ agent_without_identity = server.create_agent( CreateAgent( memory_blocks=[], - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), include_base_tools=False, ), @@ -4618,7 +4649,7 @@ def test_job_usage_stats_add_and_get(server: SyncServer, sarah_agent, default_jo step_manager.log_step( agent_id=sarah_agent.id, provider_name="openai", - model="gpt-4", + model="gpt-4o-mini", model_endpoint="https://api.openai.com/v1", context_window_limit=8192, job_id=default_job.id, @@ -4669,7 +4700,7 @@ def test_job_usage_stats_add_multiple(server: SyncServer, sarah_agent, default_j step_manager.log_step( agent_id=sarah_agent.id, provider_name="openai", - model="gpt-4", + model="gpt-4o-mini", model_endpoint="https://api.openai.com/v1", context_window_limit=8192, job_id=default_job.id, @@ -4685,7 +4716,7 @@ def test_job_usage_stats_add_multiple(server: SyncServer, sarah_agent, default_j step_manager.log_step( agent_id=sarah_agent.id, provider_name="openai", - model="gpt-4", + model="gpt-4o-mini", model_endpoint="https://api.openai.com/v1", context_window_limit=8192, job_id=default_job.id, @@ -4731,7 +4762,7 @@ def test_job_usage_stats_add_nonexistent_job(server: SyncServer, sarah_agent, de step_manager.log_step( agent_id=sarah_agent.id, provider_name="openai", - model="gpt-4", + model="gpt-4o-mini", model_endpoint="https://api.openai.com/v1", context_window_limit=8192, job_id="nonexistent_job", @@ -4757,7 +4788,7 @@ def test_list_tags(server: SyncServer, default_user, default_organization): agent_create=CreateAgent( name="tag_agent_" + str(i), memory_blocks=[], - llm_config=LLMConfig.default_config("gpt-4"), + llm_config=LLMConfig.default_config("gpt-4o-mini"), embedding_config=EmbeddingConfig.default_config(provider="openai"), tags=tags[i : i + 3], # Each agent gets 3 consecutive tags include_base_tools=False,