diff --git a/assistants/explorer-assistant/uv.lock b/assistants/explorer-assistant/uv.lock index 4886e895..f301090d 100644 --- a/assistants/explorer-assistant/uv.lock +++ b/assistants/explorer-assistant/uv.lock @@ -605,40 +605,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../libraries/python/function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../libraries/python/context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -1047,7 +1013,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -1060,7 +1026,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../libraries/python/function-registry" }, + { name = "events", editable = "../../libraries/python/events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, diff --git a/assistants/guided-conversation-assistant/uv.lock b/assistants/guided-conversation-assistant/uv.lock index 160db72f..beef0051 100644 --- a/assistants/guided-conversation-assistant/uv.lock +++ b/assistants/guided-conversation-assistant/uv.lock @@ -367,24 +367,6 @@ requires-dist = [ { name = "semantic-workbench-assistant", editable = "../../libraries/python/semantic-workbench-assistant" }, ] -[[package]] -name = "context" -version = "0.1.0" -source = { editable = "../../libraries/python/context" } -dependencies = [ - { name = "events" }, -] - -[package.metadata] -requires-dist = [{ name = "events", editable = "../../libraries/python/events" }] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "cryptography" version = "43.0.1" @@ -573,40 +555,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../libraries/python/function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../libraries/python/context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "guided-conversation" version = "0.1.0" @@ -1131,7 +1079,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -1144,7 +1092,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../libraries/python/function-registry" }, + { name = "events", editable = "../../libraries/python/events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, diff --git a/assistants/prospector-assistant/pyproject.toml b/assistants/prospector-assistant/pyproject.toml index c07d5b03..12c817bc 100644 --- a/assistants/prospector-assistant/pyproject.toml +++ b/assistants/prospector-assistant/pyproject.toml @@ -6,35 +6,33 @@ authors = [{ name = "Semantic Workbench Team" }] readme = "README.md" requires-python = ">=3.11" dependencies = [ + "assistant-extensions[attachments]>=0.1.0", + "content-safety>=0.1.0", "deepmerge>=2.0", + "document-skill>=0.1.0", + "guided-conversation>=0.1.0", "html2docx>=1.6.0", "markdown>=3.6", + "openai-client>=0.1.0", "openai>=1.3.9", - "content-safety>=0.1.0", - "chat-driver>=0.1.0", - "skill-library>=0.1.0", "posix-skill>=0.1.0", "prospector-skill>=0.1.0", - "document-skill>=0.1.0", - "guided-conversation>=0.1.0", - "openai-client>=0.1.0", - "assistant-extensions[attachments]>=0.1.0", + "skill-library>=0.1.0", ] [tool.uv] package = true [tool.uv.sources] +assistant-drive = { path = "../../libraries/python/assistant-drive", editable = true } +assistant-extensions = { path = "../../libraries/python/assistant-extensions", editable = true } content-safety = { path = "../../libraries/python/content-safety/", editable = true } +document-skill = { path = "../../libraries/python/skills/skills/document-skill", editable = true } guided-conversation = { path = "../../libraries/python/guided-conversation", editable = true } -chat-driver = { path = "../../libraries/python/chat-driver", editable = true } -skill-library = { path = "../../libraries/python/skills/skill-library", editable = true } +openai-client = { path = "../../libraries/python/openai-client", editable = true } posix-skill = { path = "../../libraries/python/skills/skills/posix-skill", editable = true } prospector-skill = { path = "../../libraries/python/skills/skills/prospector-skill", editable = true } -document-skill = { path = "../../libraries/python/skills/skills/document-skill", editable = true } -openai-client = { path = "../../libraries/python/openai-client", editable = true } -assistant-drive = { path = "../../libraries/python/assistant-drive", editable = true } -assistant-extensions = { path = "../../libraries/python/assistant-extensions", editable = true } +skill-library = { path = "../../libraries/python/skills/skill-library", editable = true } [build-system] requires = ["hatchling"] diff --git a/assistants/prospector-assistant/uv.lock b/assistants/prospector-assistant/uv.lock index 4946c570..03f50c61 100644 --- a/assistants/prospector-assistant/uv.lock +++ b/assistants/prospector-assistant/uv.lock @@ -128,7 +128,6 @@ version = "0.1.0" source = { editable = "." } dependencies = [ { name = "assistant-extensions", extra = ["attachments"] }, - { name = "chat-driver" }, { name = "content-safety" }, { name = "deepmerge" }, { name = "document-skill" }, @@ -145,7 +144,6 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "assistant-extensions", extras = ["attachments"], editable = "../../libraries/python/assistant-extensions" }, - { name = "chat-driver", editable = "../../libraries/python/chat-driver" }, { name = "content-safety", editable = "../../libraries/python/content-safety" }, { name = "deepmerge", specifier = ">=2.0" }, { name = "document-skill", editable = "../../libraries/python/skills/skills/document-skill" }, @@ -383,39 +381,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "../../libraries/python/chat-driver" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../libraries/python/context" }, - { name = "events", editable = "../../libraries/python/events" }, - { name = "function-registry", editable = "../../libraries/python/function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../../libraries/python/openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - [[package]] name = "click" version = "8.1.7" @@ -558,17 +523,17 @@ name = "document-skill" version = "0.1.0" source = { editable = "../../libraries/python/skills/skills/document-skill" } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../libraries/python/chat-driver" }, { name = "context", editable = "../../libraries/python/context" }, { name = "events", editable = "../../libraries/python/events" }, + { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "skill-library", editable = "../../libraries/python/skills/skill-library" }, ] @@ -683,40 +648,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../libraries/python/function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../libraries/python/context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "guided-conversation" version = "0.1.0" @@ -1241,7 +1172,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -1254,7 +1185,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../libraries/python/function-registry" }, + { name = "events", editable = "../../libraries/python/events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, @@ -1473,17 +1404,17 @@ name = "posix-skill" version = "0.1.0" source = { editable = "../../libraries/python/skills/skills/posix-skill" } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../libraries/python/chat-driver" }, { name = "context", editable = "../../libraries/python/context" }, { name = "events", editable = "../../libraries/python/events" }, + { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "skill-library", editable = "../../libraries/python/skills/skill-library" }, ] @@ -1508,17 +1439,17 @@ name = "prospector-skill" version = "0.1.0" source = { editable = "../../libraries/python/skills/skills/prospector-skill" } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../libraries/python/chat-driver" }, { name = "context", editable = "../../libraries/python/context" }, { name = "events", editable = "../../libraries/python/events" }, + { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "skill-library", editable = "../../libraries/python/skills/skill-library" }, ] @@ -2064,11 +1995,11 @@ name = "skill-library" version = "0.1.0" source = { editable = "../../libraries/python/skills/skill-library" } dependencies = [ - { name = "chat-driver" }, + { name = "assistant-drive" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "openai" }, + { name = "openai-client" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -2078,11 +2009,11 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../libraries/python/chat-driver" }, + { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, { name = "context", editable = "../../libraries/python/context" }, { name = "events", editable = "../../libraries/python/events" }, - { name = "function-registry", editable = "../../libraries/python/function-registry" }, { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "pydantic", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.3.4" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -2090,6 +2021,13 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "sniffio" version = "1.3.1" diff --git a/assistants/skill-assistant/README.md b/assistants/skill-assistant/README.md index 106b36fc..214eb44a 100644 --- a/assistants/skill-assistant/README.md +++ b/assistants/skill-assistant/README.md @@ -1,6 +1,6 @@ # Skill Assistant -The Skill Assistant serves as a demonstration of integrating the Skill Library within an Assistant in the Semantic Workbench. Specifically, this assistant showcases the Posix skill and the chat driver. The [Posix skill](../../libraries/python/skills/skills/posix-skill/README.md) demonstrates file system management by allowing the assistant to perform posix-style actions. The [chat driver](../../libraries/python/chat-driver/README.md) handles conversations and interacts with underlying AI models like OpenAI and Azure OpenAI. +The Skill Assistant serves as a demonstration of integrating the Skill Library within an Assistant in the Semantic Workbench. Specifically, this assistant showcases the Posix skill and the chat driver. The [Posix skill](../../libraries/python/skills/skills/posix-skill/README.md) demonstrates file system management by allowing the assistant to perform posix-style actions. The [chat driver](../../libraries/python/openai-client/openai_client/chat_driver/README.md) handles conversations and interacts with underlying AI models like OpenAI and Azure OpenAI. ## Overview diff --git a/assistants/skill-assistant/assistant/assistant_registry.py b/assistants/skill-assistant/assistant/assistant_registry.py new file mode 100644 index 00000000..473646ef --- /dev/null +++ b/assistants/skill-assistant/assistant/assistant_registry.py @@ -0,0 +1,93 @@ +import asyncio +import logging +from pathlib import Path +from typing import List + +from openai_client.chat_driver import ChatDriverConfig +from skill_library import Assistant, Skill + +from assistant.skill_event_mapper import SkillEventMapperProtocol + +logger = logging.getLogger(__name__) + + +# TODO: Put this registry in the skill library. +class AssistantRegistry: + """ + This class handles the creation and management of skill assistant instances + for this service. Each conversation has its own assistant and we start each + assistant in it's own thread so that all events are able to be + asynchronously passed on to the Semantic Workbench. + """ + + def __init__(self) -> None: + self.assistants: dict[str, Assistant] = {} + self.tasks: set[asyncio.Task] = set() + + async def get_or_create_assistant( + self, + assistant_id: str, + event_mapper: SkillEventMapperProtocol, + chat_driver_config: ChatDriverConfig, + skills: List[Skill] = [], + ) -> Assistant: + """ + Get or create an assistant for the given conversation context. + """ + assistant = self.get_assistant(assistant_id) + if not assistant: + assistant = await self.register_assistant(assistant_id, event_mapper, chat_driver_config, skills) + return assistant + + def get_assistant( + self, + assistant_id: str, + ) -> Assistant | None: + if assistant_id in self.assistants: + return self.assistants[assistant_id] + return None + + async def register_assistant( + self, + assistant_id: str, + event_mapper: SkillEventMapperProtocol, + chat_driver_config: ChatDriverConfig, + skills: List[Skill] = [], + ) -> Assistant: + """ + Define the skill assistant that you want to have backing this assistant + service. You can configure the assistant instructions and which skills + to include here. + """ + + # Create the assistant. + assistant = Assistant( + name="Assistant", + assistant_id=assistant_id, + drive_root=Path(".data") / assistant_id / "assistant", + metadrive_drive_root=Path(".data") / assistant_id / ".assistant", + chat_driver_config=chat_driver_config, + ) + assistant.register_skills(skills) + + # Assistant event consumer. + async def subscribe() -> None: + """Event consumer for the assistant.""" + async for skill_event in assistant.events: + try: + await event_mapper.map(skill_event) + except Exception: + logging.exception("Exception in skill assistant event handling") + await assistant.wait() + + # Register the assistant + self.assistants[assistant_id] = assistant + + # Start an event consumer task and save a reference. + task = asyncio.create_task(subscribe()) + self.tasks.add(task) + + # Remove the task from the set when it completes. + task.add_done_callback(self.tasks.remove) + + return assistant diff --git a/assistants/skill-assistant/assistant/config.py b/assistants/skill-assistant/assistant/config.py index 4c66a065..0fc15331 100644 --- a/assistants/skill-assistant/assistant/config.py +++ b/assistants/skill-assistant/assistant/config.py @@ -86,7 +86,8 @@ class ChatDriverConfig(BaseModel): ] = "gpt-4o" -# the workbench app builds dynamic forms based on the configuration model and UI schema +# The workbench app builds dynamic forms based on the configuration model and UI +# schema. class AssistantConfigModel(BaseModel): guardrails_prompt: Annotated[ str, @@ -129,6 +130,14 @@ class AssistantConfigModel(BaseModel): UISchema(widget="radio"), ] = CombinedContentSafetyEvaluatorConfig() + metadata_path: Annotated[ + str, + Field( + title="Metadata Path", + description="The path for assistant metadata.", + ), + ] = ".data" + # add any additional configuration fields diff --git a/assistants/skill-assistant/assistant/skill_assistant.py b/assistants/skill-assistant/assistant/skill_assistant.py index aa08e2a0..bd59aad4 100644 --- a/assistants/skill-assistant/assistant/skill_assistant.py +++ b/assistants/skill-assistant/assistant/skill_assistant.py @@ -8,8 +8,14 @@ # the skills library to create a skill-based assistant. import logging +from pathlib import Path +import openai_client from content_safety.evaluators import CombinedContentSafetyEvaluator +from openai_client.chat_driver import ChatDriverConfig + +# from form_filler_skill import FormFillerSkill +from posix_skill import PosixSkill from semantic_workbench_api_model.workbench_model import ( ConversationEvent, ConversationMessage, @@ -25,8 +31,10 @@ ConversationContext, ) +from assistant.skill_event_mapper import SkillEventMapper + +from .assistant_registry import AssistantRegistry from .config import AssistantConfigModel -from .skill_controller import AssistantRegistry logger = logging.getLogger(__name__) @@ -34,20 +42,20 @@ # define the service ID, name, and description # -# the service id to be registered in the workbench to identify the assistant +# The service id to be registered in the workbench to identify the assistant. service_id = "skill-assistant.made-exploration" -# the name of the assistant service, as it will appear in the workbench UI + +# The name of the assistant service, as it will appear in the workbench UI. service_name = "Skill Assistant" -# a description of the assistant service, as it will appear in the workbench UI + +# A description of the assistant service, as it will appear in the workbench UI. service_description = "A skills-based assistant using the Semantic Workbench Assistant SDK." -# -# create the configuration provider, using the extended configuration model -# +# Create the configuration provider, using the extended configuration model. assistant_config = BaseModelAssistantConfig(AssistantConfigModel) -# define the content safety evaluator factory +# Create the content safety interceptor. async def content_evaluator_factory(context: ConversationContext) -> ContentSafetyEvaluator: config = await assistant_config.get(context.assistant) return CombinedContentSafetyEvaluator(config.content_safety_config) @@ -71,16 +79,20 @@ async def content_evaluator_factory(context: ConversationContext) -> ContentSafe app = assistant.fastapi_app() -# The AssistantApp class provides a set of decorators for adding event handlers to respond to conversation -# events. In VS Code, typing "@assistant." (or the name of your AssistantApp instance) will show available -# events and methods. +# The AssistantApp class provides a set of decorators for adding event handlers +# to respond to conversation events. In VS Code, typing "@assistant." (or the +# name of your AssistantApp instance) will show available events and methods. # -# See the semantic-workbench-assistant AssistantApp class for more information on available events and methods. -# Examples: -# - @assistant.events.conversation.on_created (event triggered when the assistant is added to a conversation) -# - @assistant.events.conversation.participant.on_created (event triggered when a participant is added) -# - @assistant.events.conversation.message.on_created (event triggered when a new message of any type is created) -# - @assistant.events.conversation.message.chat.on_created (event triggered when a new chat message is created) +# See the semantic-workbench-assistant AssistantApp class for more information +# on available events and methods. Examples: +# - @assistant.events.conversation.on_created (event triggered when the +# assistant is added to a conversation) +# - @assistant.events.conversation.participant.on_created (event triggered when +# a participant is added) +# - @assistant.events.conversation.message.on_created (event triggered when a +# new message of any type is created) +# - @assistant.events.conversation.message.chat.on_created (event triggered when +# a new chat message is created) assistant_registry = AssistantRegistry() @@ -139,36 +151,69 @@ async def on_conversation_created(context: ConversationContext) -> None: # Core response logic for handling messages (chat or command) in the conversation. async def respond_to_conversation( - context: ConversationContext, event: ConversationEvent, message: ConversationMessage + conversation_context: ConversationContext, + event: ConversationEvent, + message: ConversationMessage, ) -> None: """ Respond to a conversation message. """ - # TODO: pass metadata to the assistant for at least adding the content safety metadata to debug + # Get the assistant configuration. + config = await assistant_config.get(conversation_context.assistant) - # get the assistant configuration - config = await assistant_config.get(context.assistant) - - # TODO: pass metadata to the assistant for at least adding the content safety metadata to debug + # TODO: pass metadata to the assistant for at least adding the content safety metadata to debug. # metadata = {"debug": {"content_safety": event.data.get(content_safety.metadata_key, {})}} - # update the participant status to indicate the assistant is thinking - await context.update_participant_me(UpdateParticipant(status="thinking...")) + # Update the participant status to indicate the assistant is thinking. + await conversation_context.update_participant_me(UpdateParticipant(status="thinking...")) + + # Get an assistant from the skill library. + assistant = assistant_registry.get_assistant(conversation_context.id) + + # Create and register an assistant if necessary. + if not assistant: + try: + async_client = openai_client.create_client(config.service_config) + chat_driver_config = ChatDriverConfig( + openai_client=async_client, + model=config.chat_driver_config.openai_model, + instructions=config.chat_driver_config.instructions, + # context will be overwritten by the assistant when initialized. + ) + assistant = await assistant_registry.register_assistant( + conversation_context.id, + SkillEventMapper(conversation_context), + chat_driver_config, + [ + PosixSkill( + sandbox_dir=Path(".data") / conversation_context.id, + chat_driver_config=chat_driver_config, + mount_dir="/mnt/data", + ), + # FormFillerSkill( + # chat_driver_config=chat_driver_config, + # ), + ], + ) + + except Exception as e: + logging.exception("exception in on_message_created") + await conversation_context.send_messages( + NewConversationMessage( + message_type=MessageType.note, + content=f"Unhandled error: {e}", + ) + ) + return + finally: + await conversation_context.update_participant_me(UpdateParticipant(status=None)) + try: - # replace the following with your own logic for processing a message created event - assistant = await assistant_registry.get_assistant( - context, - config.chat_driver_config, - config.service_config, - ) - if assistant: - await assistant.put_message(message.content) - else: - logging.error("Assistant not created") + await assistant.put_message(message.content) except Exception as e: logging.exception("exception in on_message_created") - await context.send_messages( + await conversation_context.send_messages( NewConversationMessage( message_type=MessageType.note, content=f"Unhandled error: {e}", @@ -176,4 +221,4 @@ async def respond_to_conversation( ) finally: # update the participant status to indicate the assistant is done thinking - await context.update_participant_me(UpdateParticipant(status=None)) + await conversation_context.update_participant_me(UpdateParticipant(status=None)) diff --git a/assistants/skill-assistant/assistant/skill_controller.py b/assistants/skill-assistant/assistant/skill_controller.py deleted file mode 100644 index 274af7f1..00000000 --- a/assistants/skill-assistant/assistant/skill_controller.py +++ /dev/null @@ -1,170 +0,0 @@ -import asyncio -import logging -from pathlib import Path - -import openai_client -from chat_driver import ChatDriverConfig -from document_skill import DocumentSkill -from events import events as skill_events -from posix_skill import PosixSkill -from semantic_workbench_api_model.workbench_model import ( - MessageType, - NewConversationMessage, - UpdateParticipant, -) -from semantic_workbench_assistant.assistant_app import ( - ConversationContext, -) -from skill_library import Assistant - -from . import config - -logger = logging.getLogger(__name__) - - -async def _event_mapper( - conversation_context: ConversationContext, - skill_event: skill_events.EventProtocol, -) -> None: - """ - Maps events emitted by the skill assistant (from running actions or - routines) to message types understood by the Semantic Workbench. - """ - metadata = {"debug": skill_event.metadata} if skill_event.metadata else None - - match skill_event: - case skill_events.MessageEvent(): - await conversation_context.send_messages( - NewConversationMessage( - content=skill_event.message or "", - metadata=metadata, - ) - ) - - case skill_events.InformationEvent(): - if skill_event.message: - await conversation_context.send_messages( - NewConversationMessage( - content=f"Information event: {skill_event.message}", - message_type=MessageType.note, - metadata=metadata, - ), - ) - - case skill_events.ErrorEvent(): - await conversation_context.send_messages( - NewConversationMessage( - content=skill_event.message or "", - metadata=metadata, - ) - ) - - case skill_events.StatusUpdatedEvent(): - await conversation_context.update_participant_me(UpdateParticipant(status=skill_event.message)) - - case _: - logger.warning("Unhandled event: %s", skill_event) - - -class AssistantRegistry: - """ - This class handles the creation and management of skill assistant instances - for this service. Each conversation has its own assistant and we start each - assistant in it's own thread so that all events are able to be - asynchronously passed on to the Semantic Workbench. - - """ - - def __init__(self) -> None: - self.assistants: dict[str, Assistant] = {} - self.tasks: set[asyncio.Task] = set() - - async def get_assistant( - self, - conversation_context: ConversationContext, - chat_driver_config: config.ChatDriverConfig, - service_config: openai_client.ServiceConfig, - ) -> Assistant | None: - # If the assistant already exists, return it. - if conversation_context.id in self.assistants: - return self.assistants[conversation_context.id] - - # Create a new assistant. - assistant = await self.create_assistant(conversation_context, chat_driver_config, service_config) - if not assistant: - return - - async def subscribe() -> None: - """Event consumer for the assistant.""" - async for skill_event in assistant.events: - try: - await _event_mapper(conversation_context, skill_event) - except Exception: - logging.exception("Exception in skill assistant event handling") - await assistant.wait() - - # Register the assistant - self.assistants[conversation_context.id] = assistant - # Start an event consumer task and save a reference - task = asyncio.create_task(subscribe()) - self.tasks.add(task) - # Remove the task from the set when it completes - task.add_done_callback(self.tasks.remove) - - return assistant - - async def create_assistant( - self, - conversation_context: ConversationContext, - chat_driver_config: config.ChatDriverConfig, - service_config: openai_client.ServiceConfig, - ) -> Assistant | None: - """ - Define the skill assistant that you want to have backing this assistant - service. You can configure the assistant instructions and which skills - to include here. - """ - # Get an OpenAI client. - try: - async_client = openai_client.create_client(service_config) - except Exception as e: - logging.exception("Failed to create OpenAI client") - await conversation_context.send_messages( - NewConversationMessage(message_type=MessageType.note, content=f"Could not create an OpenAI client: {e}") - ) - return - - # Create the assistant. - assistant = Assistant( - name="Assistant", - chat_driver_config=ChatDriverConfig( - openai_client=async_client, - model=chat_driver_config.openai_model, - instructions=chat_driver_config.instructions, - ), - session_id=str(conversation_context.id), - ) - - # Define the skills this assistant should have. - posix_skill = PosixSkill( - context=assistant.context, - sandbox_dir=Path(".data"), - mount_dir="/mnt/data", - chat_driver_config=ChatDriverConfig( - openai_client=async_client, - model=chat_driver_config.openai_model, - ), - ) - - document_skill = DocumentSkill( - context=assistant.context, - chat_driver_config=ChatDriverConfig( - openai_client=async_client, - model=chat_driver_config.openai_model, - ), - ) - - # Register the skills with the assistant. - assistant.register_skills([posix_skill, document_skill]) - - return assistant diff --git a/assistants/skill-assistant/assistant/skill_event_mapper.py b/assistants/skill-assistant/assistant/skill_event_mapper.py new file mode 100644 index 00000000..bf273da7 --- /dev/null +++ b/assistants/skill-assistant/assistant/skill_event_mapper.py @@ -0,0 +1,70 @@ +import logging +from typing import Protocol + +from events import events as skill_events +from semantic_workbench_api_model.workbench_model import ( + MessageType, + NewConversationMessage, + UpdateParticipant, +) +from semantic_workbench_assistant.assistant_app import ( + ConversationContext, +) + +logger = logging.getLogger(__name__) + + +# TODO: Put this protocol in the skill library. +class SkillEventMapperProtocol(Protocol): + @staticmethod + async def map( + skill_event: skill_events.EventProtocol, + ) -> None: ... + + +class SkillEventMapper(SkillEventMapperProtocol): + def __init__(self, conversation_context: ConversationContext) -> None: + self.conversation_context = conversation_context + + async def map( + self, + skill_event: skill_events.EventProtocol, + ) -> None: + """ + Maps events emitted by the skill assistant (from running actions or + routines) to message types understood by the Semantic Workbench. + """ + metadata = {"debug": skill_event.metadata} if skill_event.metadata else None + + match skill_event: + case skill_events.MessageEvent(): + await self.conversation_context.send_messages( + NewConversationMessage( + content=skill_event.message or "", + metadata=metadata, + ) + ) + + case skill_events.InformationEvent(): + if skill_event.message: + await self.conversation_context.send_messages( + NewConversationMessage( + content=f"Information event: {skill_event.message}", + message_type=MessageType.note, + metadata=metadata, + ), + ) + + case skill_events.ErrorEvent(): + await self.conversation_context.send_messages( + NewConversationMessage( + content=skill_event.message or "", + metadata=metadata, + ) + ) + + case skill_events.StatusUpdatedEvent(): + await self.conversation_context.update_participant_me(UpdateParticipant(status=skill_event.message)) + + case _: + logger.warning("Unhandled event: %s", skill_event) diff --git a/assistants/skill-assistant/pyproject.toml b/assistants/skill-assistant/pyproject.toml index e9328917..7742b8ba 100644 --- a/assistants/skill-assistant/pyproject.toml +++ b/assistants/skill-assistant/pyproject.toml @@ -7,26 +7,29 @@ readme = "README.md" requires-python = ">=3.11" dependencies = [ "azure-ai-contentsafety>=1.0.0", - "azure-identity>=1.16.0", "azure-core[aio]>=1.30.0", - "openai>=1.3.9", - "semantic-workbench-assistant>=0.1.0", + "azure-identity>=1.16.0", "content-safety>=0.1.0", - "posix-skill>=0.1.0", - "document-skill>=0.1.0", + "context>=0.1.0", + # "document-skill>=0.1.0", + "form-filler-skill>=0.1.0", "openai-client>=0.1.0", + "openai>=1.3.9", + "posix-skill>=0.1.0", + "semantic-workbench-assistant>=0.1.0", ] [tool.uv] package = true [tool.uv.sources] -# If you copy this file to your project, you should verify the relative path to the following: -semantic-workbench-assistant = { path = "../../libraries/python/semantic-workbench-assistant", editable = true } content-safety = { path = "../../libraries/python/content-safety", editable = true } -posix-skill = { path = "../../libraries/python/skills/skills/posix-skill", editable = true } -document-skill = { path = "../../libraries/python/skills/skills/document-skill", editable = true } +context = { path = "../../libraries/python/context", editable = true } +# document-skill = { path = "../../libraries/python/skills/skills/document-skill", editable = true } +form-filler-skill = { path = "../../libraries/python/skills/skills/form-filler-skill", editable = true } openai-client = { path = "../../libraries/python/openai-client", editable = true } +posix-skill = { path = "../../libraries/python/skills/skills/posix-skill", editable = true } +semantic-workbench-assistant = { path = "../../libraries/python/semantic-workbench-assistant", editable = true } [build-system] requires = ["hatchling"] diff --git a/assistants/skill-assistant/uv.lock b/assistants/skill-assistant/uv.lock index 64370905..b684af43 100644 --- a/assistants/skill-assistant/uv.lock +++ b/assistants/skill-assistant/uv.lock @@ -16,7 +16,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.10.8" +version = "3.10.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -26,53 +26,53 @@ dependencies = [ { name = "multidict" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/05/da5ff89c85444a6ade9079e73580fb3f78c6ba0e170a2472f15400d03e02/aiohttp-3.10.8.tar.gz", hash = "sha256:21f8225f7dc187018e8433c9326be01477fb2810721e048b33ac49091b19fb4a", size = 7540022 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/ca/2fc934c4c86865d0eb9c46f8f57443f0655f2a4a5c1dde60ec1d6d0f0881/aiohttp-3.10.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:33a68011a38020ed4ff41ae0dbf4a96a202562ecf2024bdd8f65385f1d07f6ef", size = 586333 }, - { url = "https://files.pythonhosted.org/packages/4a/07/7215d085dc10dd2e10f36832b2ca278f30970b4db98d5ebfed9e228d5c0c/aiohttp-3.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c7efa6616a95e3bd73b8a69691012d2ef1f95f9ea0189e42f338fae080c2fc6", size = 398817 }, - { url = "https://files.pythonhosted.org/packages/c4/e4/77b029c12d025d1e448662977f1e7c6fb33a19c42181c8d20c2791b5c5d9/aiohttp-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb9b9764cfb4459acf01c02d2a59d3e5066b06a846a364fd1749aa168efa2be", size = 390465 }, - { url = "https://files.pythonhosted.org/packages/17/f5/206e6a58a3a5be39662a07f531a6033384e361e272735437c5c15176c601/aiohttp-3.10.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7f270f4ca92760f98a42c45a58674fff488e23b144ec80b1cc6fa2effed377", size = 1306316 }, - { url = "https://files.pythonhosted.org/packages/33/e7/3b6b5ad02e367f30927bb93263127c23290f5b11900d036429f4787e1948/aiohttp-3.10.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6984dda9d79064361ab58d03f6c1e793ea845c6cfa89ffe1a7b9bb400dfd56bd", size = 1344486 }, - { url = "https://files.pythonhosted.org/packages/ae/9f/f27ba4cd2bffb4885aa35827a21878dbd3f50d6e5b205ce1107ce79edc40/aiohttp-3.10.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f6d47e392c27206701565c8df4cac6ebed28fdf6dcaea5b1eea7a4631d8e6db", size = 1378320 }, - { url = "https://files.pythonhosted.org/packages/54/76/b106eb516d327527a6b1e0409a3553745ad34480eddfd0d7cad48ddc9848/aiohttp-3.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a72f89aea712c619b2ca32c6f4335c77125ede27530ad9705f4f349357833695", size = 1292542 }, - { url = "https://files.pythonhosted.org/packages/7d/0c/c116a27253c0bc76959ab8df5a109d482c0977d4028e1b3ec7fac038bb1a/aiohttp-3.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36074b26f3263879ba8e4dbd33db2b79874a3392f403a70b772701363148b9f", size = 1251608 }, - { url = "https://files.pythonhosted.org/packages/9e/05/f9624dc401f72a3ee4cddea1a555b430e9a7be9d0cd2ab53dbec2fc78279/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e32148b4a745e70a255a1d44b5664de1f2e24fcefb98a75b60c83b9e260ddb5b", size = 1271551 }, - { url = "https://files.pythonhosted.org/packages/6d/77/19a032cfb9fdfd69591cf173c23c62992774b2ff978e4dab3038a1955e14/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5aa1a073514cf59c81ad49a4ed9b5d72b2433638cd53160fd2f3a9cfa94718db", size = 1266089 }, - { url = "https://files.pythonhosted.org/packages/12/63/58ebde5ea32cf5f19c83d6dc2c582ca5f0c42ce4cf084216a3cda4b2e34a/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3a79200a9d5e621c4623081ddb25380b713c8cf5233cd11c1aabad990bb9381", size = 1321455 }, - { url = "https://files.pythonhosted.org/packages/1a/22/d8439a280161b542a28f88794ab55917cdc672544b87db52d3c41ce8d9a1/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e45fdfcb2d5bcad83373e4808825b7512953146d147488114575780640665027", size = 1339057 }, - { url = "https://files.pythonhosted.org/packages/bc/67/1a76a69adfe3013863df4142d37059fb357146815b29596945d61fb940cb/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f78e2a78432c537ae876a93013b7bc0027ba5b93ad7b3463624c4b6906489332", size = 1298892 }, - { url = "https://files.pythonhosted.org/packages/38/13/7294cb679ab7a80e5b0d0aa97c527690cffed2f34cb8892d73ebdb4204e8/aiohttp-3.10.8-cp311-cp311-win32.whl", hash = "sha256:f8179855a4e4f3b931cb1764ec87673d3fbdcca2af496c8d30567d7b034a13db", size = 362066 }, - { url = "https://files.pythonhosted.org/packages/bc/4a/8881d4d7259427897e1a314c2724e65fd0d20084c72cac8360665f96c347/aiohttp-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:ef9b484604af05ca745b6108ca1aaa22ae1919037ae4f93aaf9a37ba42e0b835", size = 381406 }, - { url = "https://files.pythonhosted.org/packages/bb/ce/a8ff9f5bd2b36e3049cfe8d53656fed03075221ff42f946c581325bdc8fc/aiohttp-3.10.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ab2d6523575fc98896c80f49ac99e849c0b0e69cc80bf864eed6af2ae728a52b", size = 583366 }, - { url = "https://files.pythonhosted.org/packages/91/5c/75287ab8a6ae9cbe02d45ebb36b1e899c11da5eb47060e0dcb98ee30a951/aiohttp-3.10.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f5d5d5401744dda50b943d8764508d0e60cc2d3305ac1e6420935861a9d544bc", size = 395525 }, - { url = "https://files.pythonhosted.org/packages/a8/5a/aca17d71eb7e0f4611b2f28cb04e05aaebe6c7c2a7d1364e494da9722714/aiohttp-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de23085cf90911600ace512e909114385026b16324fa203cc74c81f21fd3276a", size = 390727 }, - { url = "https://files.pythonhosted.org/packages/1b/ee/c1663449864ec9dd3d2a61dde09112bea5e1d881496c36146a96fe85da62/aiohttp-3.10.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4618f0d2bf523043866a9ff8458900d8eb0a6d4018f251dae98e5f1fb699f3a8", size = 1311898 }, - { url = "https://files.pythonhosted.org/packages/8b/7e/ed2eb276fdf946a9303f3f80033555d3eaa0eadbcdd0c31b153e33b495fc/aiohttp-3.10.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21c1925541ca84f7b5e0df361c0a813a7d6a56d3b0030ebd4b220b8d232015f9", size = 1350380 }, - { url = "https://files.pythonhosted.org/packages/0c/3f/1d74a1311b14a1d69aad06775ffc1c09c195db67d951c8319220b9c64fdc/aiohttp-3.10.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:497a7d20caea8855c5429db3cdb829385467217d7feb86952a6107e033e031b9", size = 1392486 }, - { url = "https://files.pythonhosted.org/packages/9f/95/b940d71b1f61cf2ed48f2918c292609d251dba012a8e033afc0c778ed6a7/aiohttp-3.10.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c887019dbcb4af58a091a45ccf376fffe800b5531b45c1efccda4bedf87747ea", size = 1306135 }, - { url = "https://files.pythonhosted.org/packages/9b/25/b096aebc2f9b3ed738a4a667b841780b1dcd23ce5dff7dfabab4d09de4c8/aiohttp-3.10.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40d2d719c3c36a7a65ed26400e2b45b2d9ed7edf498f4df38b2ae130f25a0d01", size = 1260085 }, - { url = "https://files.pythonhosted.org/packages/9e/cf/bc024d8a848ee4feaae6a037034cf8b173a14ea9cb5c2988b6e5018abf33/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57359785f27394a8bcab0da6dcd46706d087dfebf59a8d0ad2e64a4bc2f6f94f", size = 1270968 }, - { url = "https://files.pythonhosted.org/packages/40/1d/2513347c445d1aaa694e79f4d45f80d777ea3e4d772d9480577834dc2c1c/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a961ee6f2cdd1a2be4735333ab284691180d40bad48f97bb598841bfcbfb94ec", size = 1280083 }, - { url = "https://files.pythonhosted.org/packages/22/e1/4be1b057044c3d874e795744446c682715b232281adbe94612ddc9877ee4/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe3d79d6af839ffa46fdc5d2cf34295390894471e9875050eafa584cb781508d", size = 1316638 }, - { url = "https://files.pythonhosted.org/packages/6d/c3/84492f103c724d3149bba413e1dc081e573c44013bd2cc8f4addd51cf365/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a281cba03bdaa341c70b7551b2256a88d45eead149f48b75a96d41128c240b3", size = 1343764 }, - { url = "https://files.pythonhosted.org/packages/cf/b7/50cc827dd54df087d7c30293b29fbc13a7ea45a3ac54a4a12127b271265c/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6769d71bfb1ed60321363a9bc05e94dcf05e38295ef41d46ac08919e5b00d19", size = 1306007 }, - { url = "https://files.pythonhosted.org/packages/1e/c0/a4cb21ad677757368743d73aff27047dfc0d7248cb39dec06c059b773c24/aiohttp-3.10.8-cp312-cp312-win32.whl", hash = "sha256:a3081246bab4d419697ee45e555cef5cd1def7ac193dff6f50be761d2e44f194", size = 359125 }, - { url = "https://files.pythonhosted.org/packages/d2/0f/1ecbc18eed29952393d5a9c4636bfe789dde3c98fe0a0a4759d323478e72/aiohttp-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:ab1546fc8e00676febc81c548a876c7bde32f881b8334b77f84719ab2c7d28dc", size = 379143 }, - { url = "https://files.pythonhosted.org/packages/9f/dd/3d944769ed65d3d245f8f976040654b3eae2e21d05c81f91fb450365bddf/aiohttp-3.10.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b1a012677b8e0a39e181e218de47d6741c5922202e3b0b65e412e2ce47c39337", size = 575934 }, - { url = "https://files.pythonhosted.org/packages/2a/bf/a6a1d14b0e5f90d53b1f0850204f9fafdfec7c1d99dda8aaea1dd93ba181/aiohttp-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2df786c96c57cd6b87156ba4c5f166af7b88f3fc05f9d592252fdc83d8615a3c", size = 391728 }, - { url = "https://files.pythonhosted.org/packages/0e/1b/27cc6efa6ca3e563973c7e03e8b7e26b75b4046aefea991bad42c028a906/aiohttp-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8885ca09d3a9317219c0831276bfe26984b17b2c37b7bf70dd478d17092a4772", size = 387247 }, - { url = "https://files.pythonhosted.org/packages/ae/fd/235401bd4a98ea31cdda7b3822921e2a9cbc3ca0af1958a12a2709261735/aiohttp-3.10.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dbf252ac19860e0ab56cd480d2805498f47c5a2d04f5995d8d8a6effd04b48c", size = 1286909 }, - { url = "https://files.pythonhosted.org/packages/ab/1c/8ae6b12be2ae88e94be34d96765d6cc820d61d320f33c0423de8af0cfa47/aiohttp-3.10.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2036479b6b94afaaca7d07b8a68dc0e67b0caf5f6293bb6a5a1825f5923000", size = 1323446 }, - { url = "https://files.pythonhosted.org/packages/23/09/5ebe3a2dbdd008711b659dc2f2a6135bbc055b6c8869688083f4bec6b50a/aiohttp-3.10.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:365783e1b7c40b59ed4ce2b5a7491bae48f41cd2c30d52647a5b1ee8604c68ad", size = 1368237 }, - { url = "https://files.pythonhosted.org/packages/47/22/f184c27d03d34ce71e6d4b9976a4ff845d091b725f174b09f641e4a28f63/aiohttp-3.10.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270e653b5a4b557476a1ed40e6b6ce82f331aab669620d7c95c658ef976c9c5e", size = 1282598 }, - { url = "https://files.pythonhosted.org/packages/82/f6/bae1703bfacb19bb35e3522632fc5279793070625a0b5e567b109c0f0e8d/aiohttp-3.10.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8960fabc20bfe4fafb941067cda8e23c8c17c98c121aa31c7bf0cdab11b07842", size = 1236350 }, - { url = "https://files.pythonhosted.org/packages/a4/bc/ad73aced93836b8749c70e617c5d389d17a36da9ee220cdb0804f803bd9b/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f21e8f2abed9a44afc3d15bba22e0dfc71e5fa859bea916e42354c16102b036f", size = 1250172 }, - { url = "https://files.pythonhosted.org/packages/3b/18/027a8497caf3a9c247477831d67ede58e1e42a92fd635ecdb74cf5d45c8b/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fecd55e7418fabd297fd836e65cbd6371aa4035a264998a091bbf13f94d9c44d", size = 1248783 }, - { url = "https://files.pythonhosted.org/packages/6f/d2/5080c27b656e6d478e820752d633d7a4dab4a2c4fd23a6f645b553fb9da5/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:badb51d851358cd7535b647bb67af4854b64f3c85f0d089c737f75504d5910ec", size = 1293209 }, - { url = "https://files.pythonhosted.org/packages/ae/ec/c38c8690e804cb9bf3e8c473a4a7bb339ed549cd63c469f19995269ca9ec/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e860985f30f3a015979e63e7ba1a391526cdac1b22b7b332579df7867848e255", size = 1319943 }, - { url = "https://files.pythonhosted.org/packages/df/55/d6e3a13c3f37ad7a3e60a377c96541261c1943837d240f1ab2151a96da6b/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71462f8eeca477cbc0c9700a9464e3f75f59068aed5e9d4a521a103692da72dc", size = 1281380 }, - { url = "https://files.pythonhosted.org/packages/c3/31/0b84027487fa58a124251b47f9dca781e4777a50d1c4eea4d3fc8950bd10/aiohttp-3.10.8-cp313-cp313-win32.whl", hash = "sha256:177126e971782769b34933e94fddd1089cef0fe6b82fee8a885e539f5b0f0c6a", size = 357352 }, - { url = "https://files.pythonhosted.org/packages/cb/8a/b4f3a8d0fb7f4fdb3869db6c3334e23e11878123605579e067be85f7e01f/aiohttp-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:98a4eb60e27033dee9593814ca320ee8c199489fbc6b2699d0f710584db7feb7", size = 376618 }, +sdist = { url = "https://files.pythonhosted.org/packages/17/7e/16e57e6cf20eb62481a2f9ce8674328407187950ccc602ad07c685279141/aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a", size = 7542993 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/31/3c351d17596194e5a38ef169a4da76458952b2497b4b54645b9d483cbbb0/aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f", size = 586501 }, + { url = "https://files.pythonhosted.org/packages/a4/a8/a559d09eb08478cdead6b7ce05b0c4a133ba27fcdfa91e05d2e62867300d/aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb", size = 398993 }, + { url = "https://files.pythonhosted.org/packages/c5/47/7736d4174613feef61d25332c3bd1a4f8ff5591fbd7331988238a7299485/aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871", size = 390647 }, + { url = "https://files.pythonhosted.org/packages/27/21/e9ba192a04b7160f5a8952c98a1de7cf8072ad150fa3abd454ead1ab1d7f/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c", size = 1306481 }, + { url = "https://files.pythonhosted.org/packages/cf/50/f364c01c8d0def1dc34747b2470969e216f5a37c7ece00fe558810f37013/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38", size = 1344652 }, + { url = "https://files.pythonhosted.org/packages/1d/c2/74f608e984e9b585649e2e83883facad6fa3fc1d021de87b20cc67e8e5ae/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb", size = 1378498 }, + { url = "https://files.pythonhosted.org/packages/9f/a7/05a48c7c0a7a80a5591b1203bf1b64ca2ed6a2050af918d09c05852dc42b/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7", size = 1292718 }, + { url = "https://files.pythonhosted.org/packages/7d/78/a925655018747e9790350180330032e27d6e0d7ed30bde545fae42f8c49c/aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911", size = 1251776 }, + { url = "https://files.pythonhosted.org/packages/47/9d/85c6b69f702351d1236594745a4fdc042fc43f494c247a98dac17e004026/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092", size = 1271716 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/55fc805ff9b14af818903882ece08e2235b12b73b867b521b92994c52b14/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142", size = 1266263 }, + { url = "https://files.pythonhosted.org/packages/1f/ec/d2be2ca7b063e4f91519d550dbc9c1cb43040174a322470deed90b3d3333/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9", size = 1321617 }, + { url = "https://files.pythonhosted.org/packages/c9/a3/b29f7920e1cd0a9a68a45dd3eb16140074d2efb1518d2e1f3e140357dc37/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1", size = 1339227 }, + { url = "https://files.pythonhosted.org/packages/8a/81/34b67235c47e232d807b4bbc42ba9b927c7ce9476872372fddcfd1e41b3d/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a", size = 1299068 }, + { url = "https://files.pythonhosted.org/packages/04/1f/26a7fe11b6ad3184f214733428353c89ae9fe3e4f605a657f5245c5e720c/aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94", size = 362223 }, + { url = "https://files.pythonhosted.org/packages/10/91/85dcd93f64011434359ce2666bece981f08d31bc49df33261e625b28595d/aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959", size = 381576 }, + { url = "https://files.pythonhosted.org/packages/ae/99/4c5aefe5ad06a1baf206aed6598c7cdcbc7c044c46801cd0d1ecb758cae3/aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c", size = 583536 }, + { url = "https://files.pythonhosted.org/packages/a9/36/8b3bc49b49cb6d2da40ee61ff15dbcc44fd345a3e6ab5bb20844df929821/aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28", size = 395693 }, + { url = "https://files.pythonhosted.org/packages/e1/77/0aa8660dcf11fa65d61712dbb458c4989de220a844bd69778dff25f2d50b/aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f", size = 390898 }, + { url = "https://files.pythonhosted.org/packages/38/d2/b833d95deb48c75db85bf6646de0a697e7fb5d87bd27cbade4f9746b48b1/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138", size = 1312060 }, + { url = "https://files.pythonhosted.org/packages/aa/5f/29fd5113165a0893de8efedf9b4737e0ba92dfcd791415a528f947d10299/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742", size = 1350553 }, + { url = "https://files.pythonhosted.org/packages/ad/cc/f835f74b7d344428469200105236d44606cfa448be1e7c95ca52880d9bac/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7", size = 1392646 }, + { url = "https://files.pythonhosted.org/packages/bf/fe/1332409d845ca601893bbf2d76935e0b93d41686e5f333841c7d7a4a770d/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16", size = 1306310 }, + { url = "https://files.pythonhosted.org/packages/e4/a1/25a7633a5a513278a9892e333501e2e69c83e50be4b57a62285fb7a008c3/aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8", size = 1260255 }, + { url = "https://files.pythonhosted.org/packages/f2/39/30eafe89e0e2a06c25e4762844c8214c0c0cd0fd9ffc3471694a7986f421/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6", size = 1271141 }, + { url = "https://files.pythonhosted.org/packages/5b/fc/33125df728b48391ef1fcb512dfb02072158cc10d041414fb79803463020/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a", size = 1280244 }, + { url = "https://files.pythonhosted.org/packages/3b/61/e42bf2c2934b5caa4e2ec0b5e5fd86989adb022b5ee60c2572a9d77cf6fe/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9", size = 1316805 }, + { url = "https://files.pythonhosted.org/packages/18/32/f52a5e2ae9ad3bba10e026a63a7a23abfa37c7d97aeeb9004eaa98df3ce3/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a", size = 1343930 }, + { url = "https://files.pythonhosted.org/packages/05/be/6a403b464dcab3631fe8e27b0f1d906d9e45c5e92aca97ee007e5a895560/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205", size = 1306186 }, + { url = "https://files.pythonhosted.org/packages/8e/fd/bb50fe781068a736a02bf5c7ad5f3ab53e39f1d1e63110da6d30f7605edc/aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628", size = 359289 }, + { url = "https://files.pythonhosted.org/packages/70/9e/5add7e240f77ef67c275c82cc1d08afbca57b77593118c1f6e920ae8ad3f/aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf", size = 379313 }, + { url = "https://files.pythonhosted.org/packages/b1/eb/618b1b76c7fe8082a71c9d62e3fe84c5b9af6703078caa9ec57850a12080/aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28", size = 576114 }, + { url = "https://files.pythonhosted.org/packages/aa/37/3126995d7869f8b30d05381b81a2d4fb4ec6ad313db788e009bc6d39c211/aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d", size = 391901 }, + { url = "https://files.pythonhosted.org/packages/3e/f2/8fdfc845be1f811c31ceb797968523813f8e1263ee3e9120d61253f6848f/aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79", size = 387418 }, + { url = "https://files.pythonhosted.org/packages/60/d5/33d2061d36bf07e80286e04b7e0a4de37ce04b5ebfed72dba67659a05250/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e", size = 1287073 }, + { url = "https://files.pythonhosted.org/packages/00/52/affb55be16a4747740bd630b4c002dac6c5eac42f9bb64202fc3cf3f1930/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6", size = 1323612 }, + { url = "https://files.pythonhosted.org/packages/94/f2/cddb69b975387daa2182a8442566971d6410b8a0179bb4540d81c97b1611/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42", size = 1368406 }, + { url = "https://files.pythonhosted.org/packages/c1/e4/afba7327da4d932da8c6e29aecaf855f9d52dace53ac15bfc8030a246f1b/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e", size = 1282761 }, + { url = "https://files.pythonhosted.org/packages/9f/6b/364856faa0c9031ea76e24ef0f7fef79cddd9fa8e7dba9a1771c6acc56b5/aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc", size = 1236518 }, + { url = "https://files.pythonhosted.org/packages/46/af/c382846f8356fe64a7b5908bb9b477457aa23b71be7ed551013b7b7d4d87/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a", size = 1250344 }, + { url = "https://files.pythonhosted.org/packages/87/53/294f87fc086fd0772d0ab82497beb9df67f0f27a8b3dd5742a2656db2bc6/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414", size = 1248956 }, + { url = "https://files.pythonhosted.org/packages/86/30/7d746717fe11bdfefb88bb6c09c5fc985d85c4632da8bb6018e273899254/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3", size = 1293379 }, + { url = "https://files.pythonhosted.org/packages/48/b9/45d670a834458db67a24258e9139ba61fa3bd7d69b98ecf3650c22806f8f/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67", size = 1320108 }, + { url = "https://files.pythonhosted.org/packages/72/8c/804bb2e837a175635d2000a0659eafc15b2e9d92d3d81c8f69e141ecd0b0/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b", size = 1281546 }, + { url = "https://files.pythonhosted.org/packages/89/c0/862e6a9de3d6eeb126cd9d9ea388243b70df9b871ce1a42b193b7a4a77fc/aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8", size = 357516 }, + { url = "https://files.pythonhosted.org/packages/ae/63/3e1aee3e554263f3f1011cca50d78a4894ae16ce99bf78101ac3a2f0ef74/aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151", size = 376785 }, ] [[package]] @@ -98,27 +98,28 @@ wheels = [ [[package]] name = "anyio" -version = "4.6.0" +version = "4.6.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 } +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 }, + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, ] [[package]] name = "asgi-correlation-id" -version = "4.3.3" +version = "4.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "packaging" }, { name = "starlette" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/81/15a920a166d38ce0685de33f3c5e64b86a3c112c7d646ed46d8ec1cb4047/asgi_correlation_id-4.3.3.tar.gz", hash = "sha256:25d89b52f3d32c0f3b4915a9fc38d9cffc7395960d05910bdce5c13023dc237b", size = 20052 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/ff/a6538245ac1eaa7733ec6740774e9d5add019e2c63caa29e758c16c0afdd/asgi_correlation_id-4.3.4.tar.gz", hash = "sha256:ea6bc310380373cb9f731dc2e8b2b6fb978a76afe33f7a2384f697b8d6cd811d", size = 20075 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/e1/aa53fea4e367c1ec0e69cb78c234dec4ec50d80ce02279e1ec647dea1a47/asgi_correlation_id-4.3.3-py3-none-any.whl", hash = "sha256:62ba38c359aa004c1c3e2b8e0cdf0e8ad4aa5a93864eaadc46e77d5c142a206a", size = 15253 }, + { url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262 }, ] [[package]] @@ -130,7 +131,8 @@ dependencies = [ { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, { name = "content-safety" }, - { name = "document-skill" }, + { name = "context" }, + { name = "form-filler-skill" }, { name = "openai" }, { name = "openai-client" }, { name = "posix-skill" }, @@ -143,13 +145,39 @@ requires-dist = [ { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.16.0" }, { name = "content-safety", editable = "../../libraries/python/content-safety" }, - { name = "document-skill", editable = "../../libraries/python/skills/skills/document-skill" }, + { name = "context", editable = "../../libraries/python/context" }, + { name = "form-filler-skill", editable = "../../libraries/python/skills/skills/form-filler-skill" }, { name = "openai", specifier = ">=1.3.9" }, { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "posix-skill", editable = "../../libraries/python/skills/skills/posix-skill" }, { name = "semantic-workbench-assistant", editable = "../../libraries/python/semantic-workbench-assistant" }, ] +[[package]] +name = "assistant-drive" +version = "0.1.0" +source = { editable = "../../libraries/python/assistant-drive" } +dependencies = [ + { name = "context" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../libraries/python/context" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.5.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -174,16 +202,16 @@ wheels = [ [[package]] name = "azure-core" -version = "1.31.0" +version = "1.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/7a/f79ad135a276a37e61168495697c14ba1721a52c3eab4dae2941929c79f8/azure_core-1.31.0.tar.gz", hash = "sha256:656a0dd61e1869b1506b7c6a3b31d62f15984b1a573d6326f6aa2f3e4123284b", size = 277147 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/ee/668328306a9e963a5ad9f152cd98c7adad86c822729fd1d2a01613ad1e67/azure_core-1.32.0.tar.gz", hash = "sha256:22b3c35d6b2dae14990f6c1be2912bf23ffe50b220e708a28ab1bb92b1c730e5", size = 279128 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/8e/fcb6a77d3029d2a7356f38dbc77cf7daa113b81ddab76b5593d23321e44c/azure_core-1.31.0-py3-none-any.whl", hash = "sha256:22954de3777e0250029360ef31d80448ef1be13b80a459bff80ba7073379e2cd", size = 197399 }, + { url = "https://files.pythonhosted.org/packages/39/83/325bf5e02504dbd8b4faa98197a44cdf8a325ef259b48326a2b6f17f8383/azure_core-1.32.0-py3-none-any.whl", hash = "sha256:eac191a0efb23bfa83fddf321b27b122b4ec847befa3091fa736a5c32c50d7b4", size = 198855 }, ] [package.optional-dependencies] @@ -193,7 +221,7 @@ aio = [ [[package]] name = "azure-identity" -version = "1.18.0" +version = "1.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -202,9 +230,9 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/5d/1c7da35dd640b4a95a38f980bb6b0b56c4e91d5b3d518ac11a2c4ebf5f62/azure_identity-1.18.0.tar.gz", hash = "sha256:f567579a65d8932fa913c76eddf3305101a15e5727a5e4aa5df649a0f553d4c3", size = 263322 } +sdist = { url = "https://files.pythonhosted.org/packages/aa/91/cbaeff9eb0b838f0d35b4607ac1c6195c735c8eb17db235f8f60e622934c/azure_identity-1.19.0.tar.gz", hash = "sha256:500144dc18197d7019b81501165d4fa92225f03778f17d7ca8a2a180129a9c83", size = 263058 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/71/1d1bb387b6acaa5daa3e703c70dde3d54823ccd229bd6730de6e724f296e/azure_identity-1.18.0-py3-none-any.whl", hash = "sha256:bccf6106245b49ff41d0c4cd7b72851c5a2ba3a32cef7589da246f5727f26f02", size = 187179 }, + { url = "https://files.pythonhosted.org/packages/f0/d5/3995ed12f941f4a41a273d9b1709282e825ef87ed8eab3833038fee54d59/azure_identity-1.19.0-py3-none-any.whl", hash = "sha256:e3f6558c181692d7509f09de10cca527c7dce426776454fb97df512a46527e81", size = 187587 }, ] [[package]] @@ -272,74 +300,56 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, - { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, - { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, - { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, - { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, - { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, - { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, - { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, - { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, - { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, - { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, - { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, - { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, - { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, - { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, - { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, - { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, - { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, - { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, - { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, - { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, - { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, - { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, - { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, - { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, - { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, - { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, - { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, - { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, - { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, - { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, -] - -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "../../libraries/python/chat-driver" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../libraries/python/context" }, - { name = "events", editable = "../../libraries/python/events" }, - { name = "function-registry", editable = "../../libraries/python/function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../../libraries/python/openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, + { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, + { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, + { url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 }, + { url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 }, + { url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 }, + { url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 }, + { url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 }, + { url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 }, + { url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 }, + { url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 }, + { url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 }, + { url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 }, + { url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 }, + { url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, ] [[package]] @@ -404,31 +414,31 @@ dev = [ [[package]] name = "cryptography" -version = "43.0.1" +version = "43.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222 }, - { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751 }, - { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827 }, - { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034 }, - { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407 }, - { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457 }, - { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499 }, - { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504 }, - { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456 }, - { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263 }, - { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368 }, - { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750 }, - { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925 }, - { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152 }, - { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392 }, - { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 }, - { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 }, - { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 }, +sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303 }, + { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905 }, + { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271 }, + { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606 }, + { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484 }, + { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131 }, + { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647 }, + { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873 }, + { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039 }, + { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984 }, + { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968 }, + { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754 }, + { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458 }, + { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220 }, + { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898 }, + { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592 }, + { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145 }, + { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026 }, ] [[package]] @@ -451,30 +461,11 @@ wheels = [ [[package]] name = "dnspython" -version = "2.6.1" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/7d/c871f55054e403fdfd6b8f65fd6d1c4e147ed100d3e9f9ba1fe695403939/dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc", size = 332727 } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/a1/8c5287991ddb8d3e4662f71356d9656d91ab3a36618c3dd11b280df0d255/dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50", size = 307696 }, -] - -[[package]] -name = "document-skill" -version = "0.1.0" -source = { editable = "../../libraries/python/skills/skills/document-skill" } -dependencies = [ - { name = "chat-driver" }, - { name = "context" }, - { name = "events" }, - { name = "skill-library" }, -] - -[package.metadata] -requires-dist = [ - { name = "chat-driver", editable = "../../libraries/python/chat-driver" }, - { name = "context", editable = "../../libraries/python/context" }, - { name = "events", editable = "../../libraries/python/events" }, - { name = "skill-library", editable = "../../libraries/python/skills/skill-library" }, + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, ] [[package]] @@ -503,16 +494,16 @@ requires-dist = [{ name = "pydantic", specifier = ">=2.6.1" }] [[package]] name = "fastapi" -version = "0.115.0" +version = "0.115.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/5e/bf0471f14bf6ebfbee8208148a3396d1a23298531a6cc10776c59f4c0f87/fastapi-0.115.0.tar.gz", hash = "sha256:f93b4ca3529a8ebc6fc3fcf710e5efa8de3df9b41570958abf1d97d843138004", size = 302295 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/db/5781f19bd30745885e0737ff3fdd4e63e7bc691710f9da691128bb0dc73b/fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349", size = 300737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/ab/a1f7eed031aeb1c406a6e9d45ca04bff401c8a25a30dd0e4fd2caae767c3/fastapi-0.115.0-py3-none-any.whl", hash = "sha256:17ea427674467486e997206a5ab25760f6b09e069f099b96f5b55a32fb6f1631", size = 94625 }, + { url = "https://files.pythonhosted.org/packages/99/f6/af0d1f58f86002be0cf1e2665cdd6f7a4a71cdc8a7a9438cdc9e3b5375fe/fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742", size = 94732 }, ] [package.optional-dependencies] @@ -544,76 +535,76 @@ standard = [ ] [[package]] -name = "frozenlist" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/3d/2102257e7acad73efc4a0c306ad3953f68c504c16982bbdfee3ad75d8085/frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", size = 37820 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/bc/8d33f2d84b9368da83e69e42720cff01c5e199b5a868ba4486189a4d8fa9/frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", size = 97060 }, - { url = "https://files.pythonhosted.org/packages/af/b2/904500d6a162b98a70e510e743e7ea992241b4f9add2c8063bf666ca21df/frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", size = 55347 }, - { url = "https://files.pythonhosted.org/packages/5b/9c/f12b69997d3891ddc0d7895999a00b0c6a67f66f79498c0e30f27876435d/frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", size = 53374 }, - { url = "https://files.pythonhosted.org/packages/ac/6e/e0322317b7c600ba21dec224498c0c5959b2bce3865277a7c0badae340a9/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", size = 273288 }, - { url = "https://files.pythonhosted.org/packages/a7/76/180ee1b021568dad5b35b7678616c24519af130ed3fa1e0f1ed4014e0f93/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", size = 284737 }, - { url = "https://files.pythonhosted.org/packages/05/08/40159d706a6ed983c8aca51922a93fc69f3c27909e82c537dd4054032674/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", size = 280267 }, - { url = "https://files.pythonhosted.org/packages/e0/18/9f09f84934c2b2aa37d539a322267939770362d5495f37783440ca9c1b74/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", size = 258778 }, - { url = "https://files.pythonhosted.org/packages/b3/c9/0bc5ee7e1f5cc7358ab67da0b7dfe60fbd05c254cea5c6108e7d1ae28c63/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", size = 272276 }, - { url = "https://files.pythonhosted.org/packages/12/5d/147556b73a53ad4df6da8bbb50715a66ac75c491fdedac3eca8b0b915345/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", size = 272424 }, - { url = "https://files.pythonhosted.org/packages/83/61/2087bbf24070b66090c0af922685f1d0596c24bb3f3b5223625bdeaf03ca/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", size = 260881 }, - { url = "https://files.pythonhosted.org/packages/a8/be/a235bc937dd803258a370fe21b5aa2dd3e7bfe0287a186a4bec30c6cccd6/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", size = 282327 }, - { url = "https://files.pythonhosted.org/packages/5d/e7/b2469e71f082948066b9382c7b908c22552cc705b960363c390d2e23f587/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74", size = 281502 }, - { url = "https://files.pythonhosted.org/packages/db/1b/6a5b970e55dffc1a7d0bb54f57b184b2a2a2ad0b7bca16a97ca26d73c5b5/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", size = 272292 }, - { url = "https://files.pythonhosted.org/packages/1a/05/ebad68130e6b6eb9b287dacad08ea357c33849c74550c015b355b75cc714/frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", size = 44446 }, - { url = "https://files.pythonhosted.org/packages/b3/21/c5aaffac47fd305d69df46cfbf118768cdf049a92ee6b0b5cb029d449dcf/frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", size = 50459 }, - { url = "https://files.pythonhosted.org/packages/b4/db/4cf37556a735bcdb2582f2c3fa286aefde2322f92d3141e087b8aeb27177/frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", size = 93937 }, - { url = "https://files.pythonhosted.org/packages/46/03/69eb64642ca8c05f30aa5931d6c55e50b43d0cd13256fdd01510a1f85221/frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", size = 53656 }, - { url = "https://files.pythonhosted.org/packages/3f/ab/c543c13824a615955f57e082c8a5ee122d2d5368e80084f2834e6f4feced/frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", size = 51868 }, - { url = "https://files.pythonhosted.org/packages/a9/b8/438cfd92be2a124da8259b13409224d9b19ef8f5a5b2507174fc7e7ea18f/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", size = 280652 }, - { url = "https://files.pythonhosted.org/packages/54/72/716a955521b97a25d48315c6c3653f981041ce7a17ff79f701298195bca3/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", size = 286739 }, - { url = "https://files.pythonhosted.org/packages/65/d8/934c08103637567084568e4d5b4219c1016c60b4d29353b1a5b3587827d6/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", size = 289447 }, - { url = "https://files.pythonhosted.org/packages/70/bb/d3b98d83ec6ef88f9bd63d77104a305d68a146fd63a683569ea44c3085f6/frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", size = 265466 }, - { url = "https://files.pythonhosted.org/packages/0b/f2/b8158a0f06faefec33f4dff6345a575c18095a44e52d4f10c678c137d0e0/frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", size = 281530 }, - { url = "https://files.pythonhosted.org/packages/ea/a2/20882c251e61be653764038ece62029bfb34bd5b842724fff32a5b7a2894/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", size = 281295 }, - { url = "https://files.pythonhosted.org/packages/4c/f9/8894c05dc927af2a09663bdf31914d4fb5501653f240a5bbaf1e88cab1d3/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", size = 268054 }, - { url = "https://files.pythonhosted.org/packages/37/ff/a613e58452b60166507d731812f3be253eb1229808e59980f0405d1eafbf/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", size = 286904 }, - { url = "https://files.pythonhosted.org/packages/cc/6e/0091d785187f4c2020d5245796d04213f2261ad097e0c1cf35c44317d517/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", size = 290754 }, - { url = "https://files.pythonhosted.org/packages/a5/c2/e42ad54bae8bcffee22d1e12a8ee6c7717f7d5b5019261a8c861854f4776/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", size = 282602 }, - { url = "https://files.pythonhosted.org/packages/b6/61/56bad8cb94f0357c4bc134acc30822e90e203b5cb8ff82179947de90c17f/frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", size = 44063 }, - { url = "https://files.pythonhosted.org/packages/3e/dc/96647994a013bc72f3d453abab18340b7f5e222b7b7291e3697ca1fcfbd5/frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", size = 50452 }, - { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, -] - -[[package]] -name = "function-registry" +name = "form-filler-skill" version = "0.1.0" -source = { editable = "../../libraries/python/function-registry" } +source = { editable = "../../libraries/python/skills/skills/form-filler-skill" } dependencies = [ - { name = "azure-identity" }, { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, + { name = "events" }, + { name = "openai-client" }, + { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, { name = "context", editable = "../../libraries/python/context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, + { name = "events", editable = "../../libraries/python/events" }, + { name = "openai-client", editable = "../../libraries/python/openai-client" }, + { name = "skill-library", editable = "../../libraries/python/skills/skill-library" }, ] -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, +[[package]] +name = "frozenlist" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, + { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, + { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, + { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, + { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, + { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, + { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, + { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, + { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, + { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, + { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, + { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, + { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, + { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, + { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, + { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, + { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, + { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, + { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, + { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, + { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, + { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, + { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, + { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, + { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, + { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, + { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, + { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, + { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, + { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, + { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, + { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, + { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, + { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, + { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, + { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, + { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, + { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, + { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, + { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, + { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, + { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, + { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] [[package]] @@ -640,24 +631,31 @@ wheels = [ [[package]] name = "httptools" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/1d/d77686502fced061b3ead1c35a2d70f6b281b5f723c4eff7a2277c04e4a2/httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", size = 191228 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/d1/53283b96ed823d5e4d89ee9aa0f29df5a1bdf67f148e061549a595d534e4/httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1", size = 145855 }, - { url = "https://files.pythonhosted.org/packages/80/dd/cebc9d4b1d4b70e9f3d40d1db0829a28d57ca139d0b04197713816a11996/httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0", size = 75604 }, - { url = "https://files.pythonhosted.org/packages/76/7a/45c5a9a2e9d21f7381866eb7b6ead5a84d8fe7e54e35208eeb18320a29b4/httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc", size = 324784 }, - { url = "https://files.pythonhosted.org/packages/59/23/047a89e66045232fb82c50ae57699e40f70e073ae5ccd53f54e532fbd2a2/httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2", size = 318547 }, - { url = "https://files.pythonhosted.org/packages/82/f5/50708abc7965d7d93c0ee14a148ccc6d078a508f47fe9357c79d5360f252/httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837", size = 330211 }, - { url = "https://files.pythonhosted.org/packages/e3/1e/9823ca7aab323c0e0e9dd82ce835a6e93b69f69aedffbc94d31e327f4283/httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d", size = 322174 }, - { url = "https://files.pythonhosted.org/packages/14/e4/20d28dfe7f5b5603b6b04c33bb88662ad749de51f0c539a561f235f42666/httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3", size = 55434 }, - { url = "https://files.pythonhosted.org/packages/60/13/b62e086b650752adf9094b7e62dab97f4cb7701005664544494b7956a51e/httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0", size = 146354 }, - { url = "https://files.pythonhosted.org/packages/f8/5d/9ad32b79b6c24524087e78aa3f0a2dfcf58c11c90e090e4593b35def8a86/httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2", size = 75785 }, - { url = "https://files.pythonhosted.org/packages/d0/a4/b503851c40f20bcbd453db24ed35d961f62abdae0dccc8f672cd5d350d87/httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90", size = 345396 }, - { url = "https://files.pythonhosted.org/packages/a2/9a/aa406864f3108e06f7320425a528ff8267124dead1fd72a3e9da2067f893/httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503", size = 344741 }, - { url = "https://files.pythonhosted.org/packages/cf/3a/3fd8dfb987c4247651baf2ac6f28e8e9f889d484ca1a41a9ad0f04dfe300/httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84", size = 345096 }, - { url = "https://files.pythonhosted.org/packages/80/01/379f6466d8e2edb861c1f44ccac255ed1f8a0d4c5c666a1ceb34caad7555/httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb", size = 343535 }, - { url = "https://files.pythonhosted.org/packages/d3/97/60860e9ee87a7d4712b98f7e1411730520053b9d69e9e42b0b9751809c17/httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949", size = 55660 }, +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029 }, + { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492 }, + { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891 }, + { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788 }, + { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214 }, + { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120 }, + { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565 }, + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214 }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431 }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121 }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805 }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858 }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042 }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 }, ] [[package]] @@ -696,14 +694,11 @@ wheels = [ [[package]] name = "isodate" -version = "0.6.1" +version = "0.7.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/db/7a/c0a56c7d56c7fa723988f122fa1f1ccf8c5c4ccc48efad0d214b49e5b1af/isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9", size = 28443 } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/85/7882d311924cbcfc70b1890780763e36ff0b140c7e51c110fc59a532f087/isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96", size = 41722 }, + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320 }, ] [[package]] @@ -720,34 +715,46 @@ wheels = [ [[package]] name = "jiter" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/1a/aa64be757afc614484b370a4d9fc1747dc9237b37ce464f7f9d9ca2a3d38/jiter-0.5.0.tar.gz", hash = "sha256:1d916ba875bcab5c5f7d927df998c4cb694d27dceddf3392e58beaf10563368a", size = 158300 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/5f/3ac960ed598726aae46edea916e6df4df7ff6fe084bc60774b95cf3154e6/jiter-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4c8e1ed0ef31ad29cae5ea16b9e41529eb50a7fba70600008e9f8de6376d553", size = 284131 }, - { url = "https://files.pythonhosted.org/packages/03/eb/2308fa5f5c14c97c4c7720fef9465f1fa0771826cddb4eec9866bdd88846/jiter-0.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6f16e21276074a12d8421692515b3fd6d2ea9c94fd0734c39a12960a20e85f3", size = 299310 }, - { url = "https://files.pythonhosted.org/packages/3c/f6/dba34ca10b44715fa5302b8e8d2113f72eb00a9297ddf3fa0ae4fd22d1d1/jiter-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5280e68e7740c8c128d3ae5ab63335ce6d1fb6603d3b809637b11713487af9e6", size = 332282 }, - { url = "https://files.pythonhosted.org/packages/69/f7/64e0a7439790ec47f7681adb3871c9d9c45fff771102490bbee5e92c00b7/jiter-0.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:583c57fc30cc1fec360e66323aadd7fc3edeec01289bfafc35d3b9dcb29495e4", size = 342370 }, - { url = "https://files.pythonhosted.org/packages/55/31/1efbfff2ae8e4d919144c53db19b828049ad0622a670be3bbea94a86282c/jiter-0.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26351cc14507bdf466b5f99aba3df3143a59da75799bf64a53a3ad3155ecded9", size = 363591 }, - { url = "https://files.pythonhosted.org/packages/30/c3/7ab2ca2276426a7398c6dfb651e38dbc81954c79a3bfbc36c514d8599499/jiter-0.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829df14d656b3fb87e50ae8b48253a8851c707da9f30d45aacab2aa2ba2d614", size = 378551 }, - { url = "https://files.pythonhosted.org/packages/47/e7/5d88031cd743c62199b125181a591b1671df3ff2f6e102df85c58d8f7d31/jiter-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42a4bdcf7307b86cb863b2fb9bb55029b422d8f86276a50487982d99eed7c6e", size = 319152 }, - { url = "https://files.pythonhosted.org/packages/4c/2d/09ea58e1adca9f0359f3d41ef44a1a18e59518d7c43a21f4ece9e72e28c0/jiter-0.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04d461ad0aebf696f8da13c99bc1b3e06f66ecf6cfd56254cc402f6385231c06", size = 357377 }, - { url = "https://files.pythonhosted.org/packages/7d/2f/83ff1058cb56fc3ff73e0d3c6440703ddc9cdb7f759b00cfbde8228fc435/jiter-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6375923c5f19888c9226582a124b77b622f8fd0018b843c45eeb19d9701c403", size = 511091 }, - { url = "https://files.pythonhosted.org/packages/ae/c9/4f85f97c9894382ab457382337aea0012711baaa17f2ed55c0ff25f3668a/jiter-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cec323a853c24fd0472517113768c92ae0be8f8c384ef4441d3632da8baa646", size = 492948 }, - { url = "https://files.pythonhosted.org/packages/4d/f2/2e987e0eb465e064c5f52c2f29c8d955452e3b316746e326269263bfb1b7/jiter-0.5.0-cp311-none-win32.whl", hash = "sha256:aa1db0967130b5cab63dfe4d6ff547c88b2a394c3410db64744d491df7f069bb", size = 195183 }, - { url = "https://files.pythonhosted.org/packages/ab/59/05d1c3203c349b37c4dd28b02b9b4e5915a7bcbd9319173b4548a67d2e93/jiter-0.5.0-cp311-none-win_amd64.whl", hash = "sha256:aa9d2b85b2ed7dc7697597dcfaac66e63c1b3028652f751c81c65a9f220899ae", size = 191032 }, - { url = "https://files.pythonhosted.org/packages/aa/bd/c3950e2c478161e131bed8cb67c36aed418190e2a961a1c981e69954e54b/jiter-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9f664e7351604f91dcdd557603c57fc0d551bc65cc0a732fdacbf73ad335049a", size = 283511 }, - { url = "https://files.pythonhosted.org/packages/80/1c/8ce58d8c37a589eeaaa5d07d131fd31043886f5e77ab50c00a66d869a361/jiter-0.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:044f2f1148b5248ad2c8c3afb43430dccf676c5a5834d2f5089a4e6c5bbd64df", size = 296974 }, - { url = "https://files.pythonhosted.org/packages/4d/b8/6faeff9eed8952bed93a77ea1cffae7b946795b88eafd1a60e87a67b09e0/jiter-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:702e3520384c88b6e270c55c772d4bd6d7b150608dcc94dea87ceba1b6391248", size = 331897 }, - { url = "https://files.pythonhosted.org/packages/4f/54/1d9a2209b46d39ce6f0cef3ad87c462f9c50312ab84585e6bd5541292b35/jiter-0.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:528d742dcde73fad9d63e8242c036ab4a84389a56e04efd854062b660f559544", size = 342962 }, - { url = "https://files.pythonhosted.org/packages/2a/de/90360be7fc54b2b4c2dfe79eb4ed1f659fce9c96682e6a0be4bbe71371f7/jiter-0.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf80e5fe6ab582c82f0c3331df27a7e1565e2dcf06265afd5173d809cdbf9ba", size = 363844 }, - { url = "https://files.pythonhosted.org/packages/ba/ad/ef32b173191b7a53ea8a6757b80723cba321f8469834825e8c71c96bde17/jiter-0.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44dfc9ddfb9b51a5626568ef4e55ada462b7328996294fe4d36de02fce42721f", size = 378709 }, - { url = "https://files.pythonhosted.org/packages/07/de/353ce53743c0defbbbd652e89c106a97dbbac4eb42c95920b74b5056b93a/jiter-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c451f7922992751a936b96c5f5b9bb9312243d9b754c34b33d0cb72c84669f4e", size = 319038 }, - { url = "https://files.pythonhosted.org/packages/3f/92/42d47310bf9530b9dece9e2d7c6d51cf419af5586ededaf5e66622d160e2/jiter-0.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:308fce789a2f093dca1ff91ac391f11a9f99c35369117ad5a5c6c4903e1b3e3a", size = 357763 }, - { url = "https://files.pythonhosted.org/packages/bd/8c/2bb76a9a84474d48fdd133d3445db8a4413da4e87c23879d917e000a9d87/jiter-0.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7f5ad4a7c6b0d90776fdefa294f662e8a86871e601309643de30bf94bb93a64e", size = 511031 }, - { url = "https://files.pythonhosted.org/packages/33/4f/9f23d79c0795e0a8e56e7988e8785c2dcda27e0ed37977256d50c77c6a19/jiter-0.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea189db75f8eca08807d02ae27929e890c7d47599ce3d0a6a5d41f2419ecf338", size = 493042 }, - { url = "https://files.pythonhosted.org/packages/df/67/8a4f975aa834b8aecdb6b131422390173928fd47f42f269dcc32034ab432/jiter-0.5.0-cp312-none-win32.whl", hash = "sha256:e3bbe3910c724b877846186c25fe3c802e105a2c1fc2b57d6688b9f8772026e4", size = 195405 }, - { url = "https://files.pythonhosted.org/packages/15/81/296b1e25c43db67848728cdab34ac3eb5c5cbb4955ceb3f51ae60d4a5e3d/jiter-0.5.0-cp312-none-win_amd64.whl", hash = "sha256:a586832f70c3f1481732919215f36d41c59ca080fa27a65cf23d9490e75b2ef5", size = 189720 }, +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/3d/4ca1c6b8d1d15ea747da474891f9879c0f0777e2e44e87c0be81657ed016/jiter-0.7.0.tar.gz", hash = "sha256:c061d9738535497b5509f8970584f20de1e900806b239a39a9994fc191dad630", size = 162154 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/01/ac41fe6d402da0ff454e2abaee6b8cc29ad2c97cf985f503e46ca7724aca/jiter-0.7.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:91cec0ad755bd786c9f769ce8d843af955df6a8e56b17658771b2d5cb34a3ff8", size = 292667 }, + { url = "https://files.pythonhosted.org/packages/9d/cb/d2e612729676cbe022ad732aaed9c842ac459a70808a927f7f845cfc6dc1/jiter-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:feba70a28a27d962e353e978dbb6afd798e711c04cb0b4c5e77e9d3779033a1a", size = 306403 }, + { url = "https://files.pythonhosted.org/packages/e9/db/d88002c550f6405dbf98962cc3dc1c8e66de9c4f3246abebe1582b2f919f/jiter-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d866ec066c3616cacb8535dbda38bb1d470b17b25f0317c4540182bc886ce2", size = 329020 }, + { url = "https://files.pythonhosted.org/packages/18/42/54d6527bcdea2909396849491b96a6fe595bd97ec43bdc522399c534ed33/jiter-0.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e7a7a00b6f9f18289dd563596f97ecaba6c777501a8ba04bf98e03087bcbc60", size = 347638 }, + { url = "https://files.pythonhosted.org/packages/ec/12/a3c43061d5e189def91c07472e64a569196687f60c2f86150e29a5692ce7/jiter-0.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aaf564094c7db8687f2660605e099f3d3e6ea5e7135498486674fcb78e29165", size = 373916 }, + { url = "https://files.pythonhosted.org/packages/32/08/2c7432ed26d194927ec07c1dfc08cdae5b6d302369df7fdda320d6393736/jiter-0.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4d27e09825c1b3c7a667adb500ce8b840e8fc9f630da8454b44cdd4fb0081bb", size = 390942 }, + { url = "https://files.pythonhosted.org/packages/65/9b/70f3ecbd3f18ef19e50fbe5a51bdb1c520282720896c16ae1a68b90675b8/jiter-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca7c287da9c1d56dda88da1d08855a787dbb09a7e2bd13c66a2e288700bd7c7", size = 327315 }, + { url = "https://files.pythonhosted.org/packages/2c/30/ba97e50e5fe1f58a1012257e0cfac0cc09e548b63f81982546c6dac8f1e7/jiter-0.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db19a6d160f093cbc8cd5ea2abad420b686f6c0e5fb4f7b41941ebc6a4f83cda", size = 367159 }, + { url = "https://files.pythonhosted.org/packages/a1/6d/73bb48ca87951c6c01b902258d0b792ed9404980bafceb1aa87ac43eb925/jiter-0.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e46a63c7f877cf7441ffc821c28287cfb9f533ae6ed707bde15e7d4dfafa7ae", size = 514891 }, + { url = "https://files.pythonhosted.org/packages/6f/03/50c665a3d9067c5e384604310d67a184ae3176f27f677ca0c2670bb061ac/jiter-0.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ba426fa7ff21cb119fa544b75dd3fbee6a70e55a5829709c0338d07ccd30e6d", size = 498039 }, + { url = "https://files.pythonhosted.org/packages/85/35/9fb7c7fea9b9c159d2089ea9b5ff4e3e56e2d42069456e3568dadf904e99/jiter-0.7.0-cp311-none-win32.whl", hash = "sha256:c07f55a64912b0c7982377831210836d2ea92b7bd343fca67a32212dd72e38e0", size = 198533 }, + { url = "https://files.pythonhosted.org/packages/96/19/e9b32c69c6dea404d983847e92cf86c3287b0f2f3e7621180a544c0ff153/jiter-0.7.0-cp311-none-win_amd64.whl", hash = "sha256:ed27b2c43e1b5f6c7fedc5c11d4d8bfa627de42d1143d87e39e2e83ddefd861a", size = 205728 }, + { url = "https://files.pythonhosted.org/packages/d9/7b/ed881a65e8f0989913408643b68a3a0913365c5c3884e85bae01a9679dd5/jiter-0.7.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac7930bcaaeb1e229e35c91c04ed2e9f39025b86ee9fc3141706bbf6fff4aeeb", size = 292762 }, + { url = "https://files.pythonhosted.org/packages/d8/44/d0409912bc28508abffd99b9d55baba869592c1d27f9ee1cc035ef62371e/jiter-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:571feae3e7c901a8eedde9fd2865b0dfc1432fb15cab8c675a8444f7d11b7c5d", size = 302790 }, + { url = "https://files.pythonhosted.org/packages/8d/4f/38d0e87c8863c1b1f2dbac48acca8da85f6931a7b735e7163781843170a5/jiter-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8af4df8a262fa2778b68c2a03b6e9d1cb4d43d02bea6976d46be77a3a331af1", size = 329246 }, + { url = "https://files.pythonhosted.org/packages/88/3c/1af75094cbeba25df672b3f772dc717203be843e08248a0e03ef0ca382bc/jiter-0.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd028d4165097a611eb0c7494d8c1f2aebd46f73ca3200f02a175a9c9a6f22f5", size = 347808 }, + { url = "https://files.pythonhosted.org/packages/0b/74/55f00ca01223665e1418bec76cdeebb17a5f9ffae94e886da5c9bef5abd2/jiter-0.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b487247c7836810091e9455efe56a52ec51bfa3a222237e1587d04d3e04527", size = 374011 }, + { url = "https://files.pythonhosted.org/packages/f7/ae/c1c892861796aa0adb720da75c59de5dbcf74ad51243c2aeea46681dcb16/jiter-0.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6d28a92f28814e1a9f2824dc11f4e17e1df1f44dc4fdeb94c5450d34bcb2602", size = 388863 }, + { url = "https://files.pythonhosted.org/packages/9d/a2/914587a68cba16920b1f979267a4e5c19f6977cac8fb8a6fbbd00035d0ed/jiter-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90443994bbafe134f0b34201dad3ebe1c769f0599004084e046fb249ad912425", size = 326765 }, + { url = "https://files.pythonhosted.org/packages/c8/ea/79abc48a6c9ba9ee3ccb0c194ec4cc1dd62e523c7c7003189380d00e5f16/jiter-0.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f9abf464f9faac652542ce8360cea8e68fba2b78350e8a170248f9bcc228702a", size = 367756 }, + { url = "https://files.pythonhosted.org/packages/4b/eb/ddc874819382081f9ad71cf681ee76450b17ac981f78a8db6408e7e28f34/jiter-0.7.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db7a8d99fc5f842f7d2852f06ccaed066532292c41723e5dff670c339b649f88", size = 515349 }, + { url = "https://files.pythonhosted.org/packages/af/9d/816d2d7f19070b72cf0133437cbacf99a9202f6fbbc2cfa2111fb686b0e0/jiter-0.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:15cf691ebd8693b70c94627d6b748f01e6d697d9a6e9f2bc310934fcfb7cf25e", size = 498050 }, + { url = "https://files.pythonhosted.org/packages/3e/a5/d0afb758c02d2d3c8ac3214a5be26579594d790944eaee7a47af06915e0e/jiter-0.7.0-cp312-none-win32.whl", hash = "sha256:9dcd54fa422fb66ca398bec296fed5f58e756aa0589496011cfea2abb5be38a5", size = 198912 }, + { url = "https://files.pythonhosted.org/packages/d0/8e/80b2afd0391a3530966d8fc2f9c104955ba41093b3c319ae40b25e68e323/jiter-0.7.0-cp312-none-win_amd64.whl", hash = "sha256:cc989951f73f9375b8eacd571baaa057f3d7d11b7ce6f67b9d54642e7475bfad", size = 199942 }, + { url = "https://files.pythonhosted.org/packages/50/bb/82c7180dc126687ddcc25386727b3a1688ab8eff496afe7838b69886fcc7/jiter-0.7.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:24cecd18df540963cd27c08ca5ce1d0179f229ff78066d9eecbe5add29361340", size = 292624 }, + { url = "https://files.pythonhosted.org/packages/11/c2/3b6d4596eab2ff81ebfe5bab779f457433cc2ffb8a2d1d6ab5ac187f26f6/jiter-0.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d41b46236b90b043cca73785674c23d2a67d16f226394079d0953f94e765ed76", size = 304723 }, + { url = "https://files.pythonhosted.org/packages/49/65/56f78dfccfb22e43815cad4a468b4360f8cfebecc024edb5e2a625b83a04/jiter-0.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b160db0987171365c153e406a45dcab0ee613ae3508a77bfff42515cb4ce4d6e", size = 328319 }, + { url = "https://files.pythonhosted.org/packages/fd/f2/9e3ed9ac0b122dd65250fc83cd0f0979da82f055ef6041411191f6301284/jiter-0.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1c8d91e0f0bd78602eaa081332e8ee4f512c000716f5bc54e9a037306d693a7", size = 347323 }, + { url = "https://files.pythonhosted.org/packages/42/18/24517f9f8575daf36fdac9dd53fcecde3d4c5bdd9f7b97a55e26ed2555b5/jiter-0.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997706c683195eeff192d2e5285ce64d2a610414f37da3a3f2625dcf8517cf90", size = 374073 }, + { url = "https://files.pythonhosted.org/packages/a1/b1/b368ccdeff3eabb4b293a21a94317a6f717ecc5bfbfca4eecd12ff39da3f/jiter-0.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ea52a8a0ff0229ab2920284079becd2bae0688d432fca94857ece83bb49c541", size = 388224 }, + { url = "https://files.pythonhosted.org/packages/92/1e/cc3d0655bcbc026e4b7746cb1ccab10d6eb2c29ffa64e574072db4d55f73/jiter-0.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d77449d2738cf74752bb35d75ee431af457e741124d1db5e112890023572c7c", size = 326145 }, + { url = "https://files.pythonhosted.org/packages/bb/24/d410c732326738d4f392689621ff14e10d3717efe7de9ecb97c44d8765a3/jiter-0.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8203519907a1d81d6cb00902c98e27c2d0bf25ce0323c50ca594d30f5f1fbcf", size = 366857 }, + { url = "https://files.pythonhosted.org/packages/14/a1/53df95b8248968936e7ba9eb5839918e3cfd183e56356d2961b9b29a49fc/jiter-0.7.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41d15ccc53931c822dd7f1aebf09faa3cda2d7b48a76ef304c7dbc19d1302e51", size = 514972 }, + { url = "https://files.pythonhosted.org/packages/97/c8/1876add533606ff1204450dd2564638cac7f164ff90844cb921cdf25cf68/jiter-0.7.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:febf3179b2fabf71fbd2fd52acb8594163bb173348b388649567a548f356dbf6", size = 497728 }, + { url = "https://files.pythonhosted.org/packages/94/31/1e59f246e264414b004864b63783e54aa3397be88f53dda3b01db3ae4251/jiter-0.7.0-cp313-none-win32.whl", hash = "sha256:4a8e2d866e7eda19f012444e01b55079d8e1c4c30346aaac4b97e80c54e2d6d3", size = 198660 }, + { url = "https://files.pythonhosted.org/packages/ca/96/58b3d260e212add0087563672931b1176e70bef1225839a4470ec66157a5/jiter-0.7.0-cp313-none-win_amd64.whl", hash = "sha256:7417c2b928062c496f381fb0cb50412eee5ad1d8b53dbc0e011ce45bb2de522c", size = 199305 }, ] [[package]] @@ -764,30 +771,50 @@ wheels = [ [[package]] name = "markupsafe" -version = "2.1.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219 }, - { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098 }, - { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014 }, - { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220 }, - { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756 }, - { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988 }, - { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718 }, - { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317 }, - { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670 }, - { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224 }, - { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 }, - { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 }, - { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 }, - { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 }, - { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 }, - { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 }, - { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 }, - { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 }, - { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 }, - { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 }, +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, ] [[package]] @@ -882,7 +909,7 @@ wheels = [ [[package]] name = "openai" -version = "1.51.0" +version = "1.54.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -894,9 +921,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/af/cc59b1447f5a02bb1f25b9b0cd94b607aa2c969a81d9a244d4067f91f6fe/openai-1.51.0.tar.gz", hash = "sha256:8dc4f9d75ccdd5466fc8c99a952186eddceb9fd6ba694044773f3736a847149d", size = 306880 } +sdist = { url = "https://files.pythonhosted.org/packages/da/26/f1a79d8332ac5ed38fdc347701aa4a7ad8f8f66ec3f9880122c455d7ffb1/openai-1.54.2.tar.gz", hash = "sha256:0dbb8f2bb36f7ff1d200d103b9f95c35773ed3248e3a697b02f5a39015627e5e", size = 312551 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/08/9f22356d4fbd273f734db1e6663b7ca6987943080567f5580471022e57ca/openai-1.51.0-py3-none-any.whl", hash = "sha256:d9affafb7e51e5a27dce78589d4964ce4d6f6d560307265933a94b2e3f3c5d2c", size = 383533 }, + { url = "https://files.pythonhosted.org/packages/f6/0f/ea8717dedbef16fa61a0bdeb0b7ae96ff574933ed5932102d3f5a4ce01a1/openai-1.54.2-py3-none-any.whl", hash = "sha256:77010b439e69d37f67cc2f44eaa62b2b6d5a60add2d8636e4603c0e762982708", size = 389315 }, ] [[package]] @@ -907,7 +934,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -920,7 +947,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../libraries/python/function-registry" }, + { name = "events", editable = "../../libraries/python/events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, @@ -931,6 +958,15 @@ requires-dist = [ [package.metadata.requires-dev] dev = [{ name = "pytest", specifier = ">=8.3.3" }] +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + [[package]] name = "pillow" version = "11.0.0" @@ -997,20 +1033,77 @@ name = "posix-skill" version = "0.1.0" source = { editable = "../../libraries/python/skills/skills/posix-skill" } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../libraries/python/chat-driver" }, { name = "context", editable = "../../libraries/python/context" }, { name = "events", editable = "../../libraries/python/events" }, + { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "skill-library", editable = "../../libraries/python/skills/skill-library" }, ] +[[package]] +name = "propcache" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/4d/5e5a60b78dbc1d464f8a7bbaeb30957257afdc8512cbb9dfd5659304f5cd/propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70", size = 40951 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/1c/71eec730e12aec6511e702ad0cd73c2872eccb7cad39de8ba3ba9de693ef/propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354", size = 80811 }, + { url = "https://files.pythonhosted.org/packages/89/c3/7e94009f9a4934c48a371632197406a8860b9f08e3f7f7d922ab69e57a41/propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de", size = 46365 }, + { url = "https://files.pythonhosted.org/packages/c0/1d/c700d16d1d6903aeab28372fe9999762f074b80b96a0ccc953175b858743/propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87", size = 45602 }, + { url = "https://files.pythonhosted.org/packages/2e/5e/4a3e96380805bf742712e39a4534689f4cddf5fa2d3a93f22e9fd8001b23/propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016", size = 236161 }, + { url = "https://files.pythonhosted.org/packages/a5/85/90132481183d1436dff6e29f4fa81b891afb6cb89a7306f32ac500a25932/propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb", size = 244938 }, + { url = "https://files.pythonhosted.org/packages/4a/89/c893533cb45c79c970834274e2d0f6d64383ec740be631b6a0a1d2b4ddc0/propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2", size = 243576 }, + { url = "https://files.pythonhosted.org/packages/8c/56/98c2054c8526331a05f205bf45cbb2cda4e58e56df70e76d6a509e5d6ec6/propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4", size = 236011 }, + { url = "https://files.pythonhosted.org/packages/2d/0c/8b8b9f8a6e1abd869c0fa79b907228e7abb966919047d294ef5df0d136cf/propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504", size = 224834 }, + { url = "https://files.pythonhosted.org/packages/18/bb/397d05a7298b7711b90e13108db697732325cafdcd8484c894885c1bf109/propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178", size = 224946 }, + { url = "https://files.pythonhosted.org/packages/25/19/4fc08dac19297ac58135c03770b42377be211622fd0147f015f78d47cd31/propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d", size = 217280 }, + { url = "https://files.pythonhosted.org/packages/7e/76/c79276a43df2096ce2aba07ce47576832b1174c0c480fe6b04bd70120e59/propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2", size = 220088 }, + { url = "https://files.pythonhosted.org/packages/c3/9a/8a8cf428a91b1336b883f09c8b884e1734c87f724d74b917129a24fe2093/propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db", size = 233008 }, + { url = "https://files.pythonhosted.org/packages/25/7b/768a8969abd447d5f0f3333df85c6a5d94982a1bc9a89c53c154bf7a8b11/propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b", size = 237719 }, + { url = "https://files.pythonhosted.org/packages/ed/0d/e5d68ccc7976ef8b57d80613ac07bbaf0614d43f4750cf953f0168ef114f/propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b", size = 227729 }, + { url = "https://files.pythonhosted.org/packages/05/64/17eb2796e2d1c3d0c431dc5f40078d7282f4645af0bb4da9097fbb628c6c/propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1", size = 40473 }, + { url = "https://files.pythonhosted.org/packages/83/c5/e89fc428ccdc897ade08cd7605f174c69390147526627a7650fb883e0cd0/propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71", size = 44921 }, + { url = "https://files.pythonhosted.org/packages/7c/46/a41ca1097769fc548fc9216ec4c1471b772cc39720eb47ed7e38ef0006a9/propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2", size = 80800 }, + { url = "https://files.pythonhosted.org/packages/75/4f/93df46aab9cc473498ff56be39b5f6ee1e33529223d7a4d8c0a6101a9ba2/propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7", size = 46443 }, + { url = "https://files.pythonhosted.org/packages/0b/17/308acc6aee65d0f9a8375e36c4807ac6605d1f38074b1581bd4042b9fb37/propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8", size = 45676 }, + { url = "https://files.pythonhosted.org/packages/65/44/626599d2854d6c1d4530b9a05e7ff2ee22b790358334b475ed7c89f7d625/propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793", size = 246191 }, + { url = "https://files.pythonhosted.org/packages/f2/df/5d996d7cb18df076debae7d76ac3da085c0575a9f2be6b1f707fe227b54c/propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09", size = 251791 }, + { url = "https://files.pythonhosted.org/packages/2e/6d/9f91e5dde8b1f662f6dd4dff36098ed22a1ef4e08e1316f05f4758f1576c/propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89", size = 253434 }, + { url = "https://files.pythonhosted.org/packages/3c/e9/1b54b7e26f50b3e0497cd13d3483d781d284452c2c50dd2a615a92a087a3/propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e", size = 248150 }, + { url = "https://files.pythonhosted.org/packages/a7/ef/a35bf191c8038fe3ce9a414b907371c81d102384eda5dbafe6f4dce0cf9b/propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9", size = 233568 }, + { url = "https://files.pythonhosted.org/packages/97/d9/d00bb9277a9165a5e6d60f2142cd1a38a750045c9c12e47ae087f686d781/propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4", size = 229874 }, + { url = "https://files.pythonhosted.org/packages/8e/78/c123cf22469bdc4b18efb78893e69c70a8b16de88e6160b69ca6bdd88b5d/propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c", size = 225857 }, + { url = "https://files.pythonhosted.org/packages/31/1b/fd6b2f1f36d028820d35475be78859d8c89c8f091ad30e377ac49fd66359/propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887", size = 227604 }, + { url = "https://files.pythonhosted.org/packages/99/36/b07be976edf77a07233ba712e53262937625af02154353171716894a86a6/propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57", size = 238430 }, + { url = "https://files.pythonhosted.org/packages/0d/64/5822f496c9010e3966e934a011ac08cac8734561842bc7c1f65586e0683c/propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23", size = 244814 }, + { url = "https://files.pythonhosted.org/packages/fd/bd/8657918a35d50b18a9e4d78a5df7b6c82a637a311ab20851eef4326305c1/propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348", size = 235922 }, + { url = "https://files.pythonhosted.org/packages/a8/6f/ec0095e1647b4727db945213a9f395b1103c442ef65e54c62e92a72a3f75/propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5", size = 40177 }, + { url = "https://files.pythonhosted.org/packages/20/a2/bd0896fdc4f4c1db46d9bc361c8c79a9bf08ccc08ba054a98e38e7ba1557/propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3", size = 44446 }, + { url = "https://files.pythonhosted.org/packages/a8/a7/5f37b69197d4f558bfef5b4bceaff7c43cc9b51adf5bd75e9081d7ea80e4/propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7", size = 78120 }, + { url = "https://files.pythonhosted.org/packages/c8/cd/48ab2b30a6b353ecb95a244915f85756d74f815862eb2ecc7a518d565b48/propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763", size = 45127 }, + { url = "https://files.pythonhosted.org/packages/a5/ba/0a1ef94a3412aab057bd996ed5f0ac7458be5bf469e85c70fa9ceb43290b/propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d", size = 44419 }, + { url = "https://files.pythonhosted.org/packages/b4/6c/ca70bee4f22fa99eacd04f4d2f1699be9d13538ccf22b3169a61c60a27fa/propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a", size = 229611 }, + { url = "https://files.pythonhosted.org/packages/19/70/47b872a263e8511ca33718d96a10c17d3c853aefadeb86dc26e8421184b9/propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b", size = 234005 }, + { url = "https://files.pythonhosted.org/packages/4f/be/3b0ab8c84a22e4a3224719099c1229ddfdd8a6a1558cf75cb55ee1e35c25/propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb", size = 237270 }, + { url = "https://files.pythonhosted.org/packages/04/d8/f071bb000d4b8f851d312c3c75701e586b3f643fe14a2e3409b1b9ab3936/propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf", size = 231877 }, + { url = "https://files.pythonhosted.org/packages/93/e7/57a035a1359e542bbb0a7df95aad6b9871ebee6dce2840cb157a415bd1f3/propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2", size = 217848 }, + { url = "https://files.pythonhosted.org/packages/f0/93/d1dea40f112ec183398fb6c42fde340edd7bab202411c4aa1a8289f461b6/propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f", size = 216987 }, + { url = "https://files.pythonhosted.org/packages/62/4c/877340871251145d3522c2b5d25c16a1690ad655fbab7bb9ece6b117e39f/propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136", size = 212451 }, + { url = "https://files.pythonhosted.org/packages/7c/bb/a91b72efeeb42906ef58ccf0cdb87947b54d7475fee3c93425d732f16a61/propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325", size = 212879 }, + { url = "https://files.pythonhosted.org/packages/9b/7f/ee7fea8faac57b3ec5d91ff47470c6c5d40d7f15d0b1fccac806348fa59e/propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44", size = 222288 }, + { url = "https://files.pythonhosted.org/packages/ff/d7/acd67901c43d2e6b20a7a973d9d5fd543c6e277af29b1eb0e1f7bd7ca7d2/propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83", size = 228257 }, + { url = "https://files.pythonhosted.org/packages/8d/6f/6272ecc7a8daad1d0754cfc6c8846076a8cb13f810005c79b15ce0ef0cf2/propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544", size = 221075 }, + { url = "https://files.pythonhosted.org/packages/7c/bd/c7a6a719a6b3dd8b3aeadb3675b5783983529e4a3185946aa444d3e078f6/propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032", size = 39654 }, + { url = "https://files.pythonhosted.org/packages/88/e7/0eef39eff84fa3e001b44de0bd41c7c0e3432e7648ffd3d64955910f002d/propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e", size = 43705 }, + { url = "https://files.pythonhosted.org/packages/3d/b6/e6d98278f2d49b22b4d033c9f792eda783b9ab2094b041f013fc69bcde87/propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036", size = 11603 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -1083,15 +1176,15 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.5.2" +version = "2.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/27/0bed9dd26b93328b60a1402febc780e7be72b42847fa8b5c94b7d0aeb6d1/pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0", size = 70938 } +sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646 } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/8d/29e82e333f32d9e2051c10764b906c2a6cd140992910b5f49762790911ba/pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907", size = 26864 }, + { url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595 }, ] [[package]] @@ -1163,24 +1256,27 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.12" +version = "0.0.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/6e/7ecfe1366b9270f7f475c76fcfa28812493a6a1abd489b2433851a444f4f/python_multipart-0.0.12.tar.gz", hash = "sha256:045e1f98d719c1ce085ed7f7e1ef9d8ccc8c02ba02b5566d5f7521410ced58cb", size = 35713 } +sdist = { url = "https://files.pythonhosted.org/packages/40/22/edea41c2d4a22e666c0c7db7acdcbf7bc8c1c1f7d3b3ca246ec982fec612/python_multipart-0.0.17.tar.gz", hash = "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538", size = 36452 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/0b/c316262244abea7481f95f1e91d7575f3dfcf6455d56d1ffe9839c582eb1/python_multipart-0.0.12-py3-none-any.whl", hash = "sha256:43dcf96cf65888a9cd3423544dd0d75ac10f7aa0c3c28a175bbcd00c9ce1aebf", size = 23246 }, + { url = "https://files.pythonhosted.org/packages/b4/fb/275137a799169392f1fa88fff2be92f16eee38e982720a8aaadefc4a36b2/python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d", size = 24453 }, ] [[package]] name = "pywin32" -version = "306" +version = "308" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 }, - { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 }, - { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 }, - { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 }, - { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 }, - { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 }, + { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, + { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, + { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, ] [[package]] @@ -1288,15 +1384,15 @@ wheels = [ [[package]] name = "rich" -version = "13.9.1" +version = "13.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/78/87d00a1df7c457ad9aa0139f01b8a11c67209f27f927c503b0109bf2ed6c/rich-13.9.1.tar.gz", hash = "sha256:097cffdf85db1babe30cc7deba5ab3a29e1b9885047dab24c57e9a7f8a9c1466", size = 222907 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/71/cd9549551f1aa11cf7e5f92bae5817979e8b3a19e31e8810c15f3f45c311/rich-13.9.1-py3-none-any.whl", hash = "sha256:b340e739f30aa58921dc477b8adaa9ecdb7cecc217be01d93730ee1bc8aa83be", size = 242147 }, + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, ] [[package]] @@ -1372,11 +1468,11 @@ name = "skill-library" version = "0.1.0" source = { editable = "../../libraries/python/skills/skill-library" } dependencies = [ - { name = "chat-driver" }, + { name = "assistant-drive" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "openai" }, + { name = "openai-client" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -1386,11 +1482,11 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../libraries/python/chat-driver" }, + { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, { name = "context", editable = "../../libraries/python/context" }, { name = "events", editable = "../../libraries/python/events" }, - { name = "function-registry", editable = "../../libraries/python/function-registry" }, { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../libraries/python/openai-client" }, { name = "pydantic", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.3.4" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1398,6 +1494,13 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1409,52 +1512,56 @@ wheels = [ [[package]] name = "starlette" -version = "0.38.6" +version = "0.41.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/b4/e25c3b688ef703d85e55017c6edd0cbf38e5770ab748234363d54ff0251a/starlette-0.38.6.tar.gz", hash = "sha256:863a1588f5574e70a821dadefb41e4881ea451a47a3cd1b4df359d4ffefe5ead", size = 2569491 } +sdist = { url = "https://files.pythonhosted.org/packages/3e/da/1fb4bdb72ae12b834becd7e1e7e47001d32f91ec0ce8d7bc1b618d9f0bd9/starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62", size = 2573867 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/9c/93f7bc03ff03199074e81974cc148908ead60dcf189f68ba1761a0ee35cf/starlette-0.38.6-py3-none-any.whl", hash = "sha256:4517a1409e2e73ee4951214ba012052b9e16f60e90d73cfb06192c19203bbb05", size = 71451 }, + { url = "https://files.pythonhosted.org/packages/54/43/f185bfd0ca1d213beb4293bed51d92254df23d8ceaf6c0e17146d508a776/starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d", size = 73259 }, ] [[package]] name = "tiktoken" -version = "0.7.0" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/4a/abaec53e93e3ef37224a4dd9e2fc6bb871e7a538c2b6b9d2a6397271daf4/tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6", size = 33437 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/eb/57492b2568eea1d546da5cc1ae7559d924275280db80ba07e6f9b89a914b/tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f", size = 961468 }, - { url = "https://files.pythonhosted.org/packages/30/ef/e07dbfcb2f85c84abaa1b035a9279575a8da0236305491dc22ae099327f7/tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f", size = 907005 }, - { url = "https://files.pythonhosted.org/packages/ea/9b/f36db825b1e9904c3a2646439cb9923fc1e09208e2e071c6d9dd64ead131/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b", size = 1049183 }, - { url = "https://files.pythonhosted.org/packages/61/b4/b80d1fe33015e782074e96bbbf4108ccd283b8deea86fb43c15d18b7c351/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992", size = 1080830 }, - { url = "https://files.pythonhosted.org/packages/2a/40/c66ff3a21af6d62a7e0ff428d12002c4e0389f776d3ff96dcaa0bb354eee/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1", size = 1092967 }, - { url = "https://files.pythonhosted.org/packages/2e/80/f4c9e255ff236e6a69ce44b927629cefc1b63d3a00e2d1c9ed540c9492d2/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89", size = 1142682 }, - { url = "https://files.pythonhosted.org/packages/b1/10/c04b4ff592a5f46b28ebf4c2353f735c02ae7f0ce1b165d00748ced6467e/tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb", size = 799009 }, - { url = "https://files.pythonhosted.org/packages/1d/46/4cdda4186ce900608f522da34acf442363346688c71b938a90a52d7b84cc/tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908", size = 960446 }, - { url = "https://files.pythonhosted.org/packages/b6/30/09ced367d280072d7a3e21f34263dfbbf6378661e7a0f6414e7c18971083/tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410", size = 906652 }, - { url = "https://files.pythonhosted.org/packages/e6/7b/c949e4954441a879a67626963dff69096e3c774758b9f2bb0853f7b4e1e7/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704", size = 1047904 }, - { url = "https://files.pythonhosted.org/packages/50/81/1842a22f15586072280364c2ab1e40835adaf64e42fe80e52aff921ee021/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350", size = 1079836 }, - { url = "https://files.pythonhosted.org/packages/6d/87/51a133a3d5307cf7ae3754249b0faaa91d3414b85c3d36f80b54d6817aa6/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4", size = 1092472 }, - { url = "https://files.pythonhosted.org/packages/a5/1f/c93517dc6d3b2c9e988b8e24f87a8b2d4a4ab28920a3a3f3ea338397ae0c/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97", size = 1141881 }, - { url = "https://files.pythonhosted.org/packages/bf/4b/48ca098cb580c099b5058bf62c4cb5e90ca6130fa43ef4df27088536245b/tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f", size = 799281 }, +sdist = { url = "https://files.pythonhosted.org/packages/37/02/576ff3a6639e755c4f70997b2d315f56d6d71e0d046f4fb64cb81a3fb099/tiktoken-0.8.0.tar.gz", hash = "sha256:9ccbb2740f24542534369c5635cfd9b2b3c2490754a78ac8831d99f89f94eeb2", size = 35107 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/1e/ca48e7bfeeccaf76f3a501bd84db1fa28b3c22c9d1a1f41af9fb7579c5f6/tiktoken-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d622d8011e6d6f239297efa42a2657043aaed06c4f68833550cac9e9bc723ef1", size = 1039700 }, + { url = "https://files.pythonhosted.org/packages/8c/f8/f0101d98d661b34534769c3818f5af631e59c36ac6d07268fbfc89e539ce/tiktoken-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2efaf6199717b4485031b4d6edb94075e4d79177a172f38dd934d911b588d54a", size = 982413 }, + { url = "https://files.pythonhosted.org/packages/ac/3c/2b95391d9bd520a73830469f80a96e3790e6c0a5ac2444f80f20b4b31051/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5637e425ce1fc49cf716d88df3092048359a4b3bbb7da762840426e937ada06d", size = 1144242 }, + { url = "https://files.pythonhosted.org/packages/01/c4/c4a4360de845217b6aa9709c15773484b50479f36bb50419c443204e5de9/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb0e352d1dbe15aba082883058b3cce9e48d33101bdaac1eccf66424feb5b47", size = 1176588 }, + { url = "https://files.pythonhosted.org/packages/f8/a3/ef984e976822cd6c2227c854f74d2e60cf4cd6fbfca46251199914746f78/tiktoken-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56edfefe896c8f10aba372ab5706b9e3558e78db39dd497c940b47bf228bc419", size = 1237261 }, + { url = "https://files.pythonhosted.org/packages/1e/86/eea2309dc258fb86c7d9b10db536434fc16420feaa3b6113df18b23db7c2/tiktoken-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:326624128590def898775b722ccc327e90b073714227175ea8febbc920ac0a99", size = 884537 }, + { url = "https://files.pythonhosted.org/packages/c1/22/34b2e136a6f4af186b6640cbfd6f93400783c9ef6cd550d9eab80628d9de/tiktoken-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:881839cfeae051b3628d9823b2e56b5cc93a9e2efb435f4cf15f17dc45f21586", size = 1039357 }, + { url = "https://files.pythonhosted.org/packages/04/d2/c793cf49c20f5855fd6ce05d080c0537d7418f22c58e71f392d5e8c8dbf7/tiktoken-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe9399bdc3f29d428f16a2f86c3c8ec20be3eac5f53693ce4980371c3245729b", size = 982616 }, + { url = "https://files.pythonhosted.org/packages/b3/a1/79846e5ef911cd5d75c844de3fa496a10c91b4b5f550aad695c5df153d72/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a58deb7075d5b69237a3ff4bb51a726670419db6ea62bdcd8bd80c78497d7ab", size = 1144011 }, + { url = "https://files.pythonhosted.org/packages/26/32/e0e3a859136e95c85a572e4806dc58bf1ddf651108ae8b97d5f3ebe1a244/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2908c0d043a7d03ebd80347266b0e58440bdef5564f84f4d29fb235b5df3b04", size = 1175432 }, + { url = "https://files.pythonhosted.org/packages/c7/89/926b66e9025b97e9fbabeaa59048a736fe3c3e4530a204109571104f921c/tiktoken-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:294440d21a2a51e12d4238e68a5972095534fe9878be57d905c476017bff99fc", size = 1236576 }, + { url = "https://files.pythonhosted.org/packages/45/e2/39d4aa02a52bba73b2cd21ba4533c84425ff8786cc63c511d68c8897376e/tiktoken-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d8f3192733ac4d77977432947d563d7e1b310b96497acd3c196c9bddb36ed9db", size = 883824 }, + { url = "https://files.pythonhosted.org/packages/e3/38/802e79ba0ee5fcbf240cd624143f57744e5d411d2e9d9ad2db70d8395986/tiktoken-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:02be1666096aff7da6cbd7cdaa8e7917bfed3467cd64b38b1f112e96d3b06a24", size = 1039648 }, + { url = "https://files.pythonhosted.org/packages/b1/da/24cdbfc302c98663fbea66f5866f7fa1048405c7564ab88483aea97c3b1a/tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94ff53c5c74b535b2cbf431d907fc13c678bbd009ee633a2aca269a04389f9a", size = 982763 }, + { url = "https://files.pythonhosted.org/packages/e4/f0/0ecf79a279dfa41fc97d00adccf976ecc2556d3c08ef3e25e45eb31f665b/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b231f5e8982c245ee3065cd84a4712d64692348bc609d84467c57b4b72dcbc5", size = 1144417 }, + { url = "https://files.pythonhosted.org/packages/ab/d3/155d2d4514f3471a25dc1d6d20549ef254e2aa9bb5b1060809b1d3b03d3a/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4177faa809bd55f699e88c96d9bb4635d22e3f59d635ba6fd9ffedf7150b9953", size = 1175108 }, + { url = "https://files.pythonhosted.org/packages/19/eb/5989e16821ee8300ef8ee13c16effc20dfc26c777d05fbb6825e3c037b81/tiktoken-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5376b6f8dc4753cd81ead935c5f518fa0fbe7e133d9e25f648d8c4dabdd4bad7", size = 1236520 }, + { url = "https://files.pythonhosted.org/packages/40/59/14b20465f1d1cb89cfbc96ec27e5617b2d41c79da12b5e04e96d689be2a7/tiktoken-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:18228d624807d66c87acd8f25fc135665617cab220671eb65b50f5d70fa51f69", size = 883849 }, ] [[package]] name = "tqdm" -version = "4.66.5" +version = "4.67.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "platform_system == 'Windows'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/4f/0153c21dc5779a49a0598c445b1978126b1344bab9ee71e53e44877e14e0/tqdm-4.67.0.tar.gz", hash = "sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a", size = 169739 } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351 }, + { url = "https://files.pythonhosted.org/packages/2b/78/57043611a16c655c8350b4c01b8d6abfb38cc2acb475238b62c2146186d7/tqdm-4.67.0-py3-none-any.whl", hash = "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be", size = 78590 }, ] [[package]] @@ -1492,15 +1599,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.31.0" +version = "0.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/96/ee52d900f8e41cc35eaebfda76f3619c2e45b741f3ee957d6fe32be1b2aa/uvicorn-0.31.0.tar.gz", hash = "sha256:13bc21373d103859f68fe739608e2eb054a816dea79189bc3ca08ea89a275906", size = 77140 } +sdist = { url = "https://files.pythonhosted.org/packages/e0/fc/1d785078eefd6945f3e5bab5c076e4230698046231eb0f3747bc5c8fa992/uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e", size = 77564 } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/12/206aca5442524d16be7702d08b453d7c274c86fd759266b1f709d4ef43ba/uvicorn-0.31.0-py3-none-any.whl", hash = "sha256:cac7be4dd4d891c363cd942160a7b02e69150dcbc7a36be04d5f4af4b17c8ced", size = 63656 }, + { url = "https://files.pythonhosted.org/packages/eb/14/78bd0e95dd2444b6caacbca2b730671d4295ccb628ef58b81bee903629df/uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82", size = 63723 }, ] [package.optional-dependencies] @@ -1516,22 +1623,28 @@ standard = [ [[package]] name = "uvloop" -version = "0.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/f1/dc9577455e011ad43d9379e836ee73f40b4f99c02946849a44f7ae64835e/uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469", size = 2329938 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/bf/45828beccf685b7ed9638d9b77ef382b470c6ca3b5bff78067e02ffd5663/uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037", size = 1320593 }, - { url = "https://files.pythonhosted.org/packages/27/c0/3c24e50bee7802a2add96ca9f0d5eb0ebab07e0a5615539d38aeb89499b9/uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9", size = 736676 }, - { url = "https://files.pythonhosted.org/packages/83/ce/ffa3c72954eae36825acfafd2b6a9221d79abd2670c0d25e04d6ef4a2007/uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e", size = 3494573 }, - { url = "https://files.pythonhosted.org/packages/46/6d/4caab3a36199ba52b98d519feccfcf48921d7a6649daf14a93c7e77497e9/uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756", size = 3489932 }, - { url = "https://files.pythonhosted.org/packages/e4/4f/49c51595bd794945c88613df88922c38076eae2d7653f4624aa6f4980b07/uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0", size = 4185596 }, - { url = "https://files.pythonhosted.org/packages/b8/94/7e256731260d313f5049717d1c4582d52a3b132424c95e16954a50ab95d3/uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf", size = 4185746 }, - { url = "https://files.pythonhosted.org/packages/2d/64/31cbd379d6e260ac8de3f672f904e924f09715c3f192b09f26cc8e9f574c/uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d", size = 1324302 }, - { url = "https://files.pythonhosted.org/packages/1e/6b/9207e7177ff30f78299401f2e1163ea41130d4fd29bcdc6d12572c06b728/uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e", size = 738105 }, - { url = "https://files.pythonhosted.org/packages/c1/ba/b64b10f577519d875992dc07e2365899a1a4c0d28327059ce1e1bdfb6854/uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9", size = 4090658 }, - { url = "https://files.pythonhosted.org/packages/0a/f8/5ceea6876154d926604f10c1dd896adf9bce6d55a55911364337b8a5ed8d/uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab", size = 4173357 }, - { url = "https://files.pythonhosted.org/packages/18/b2/117ab6bfb18274753fbc319607bf06e216bd7eea8be81d5bac22c912d6a7/uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5", size = 4029868 }, - { url = "https://files.pythonhosted.org/packages/6f/52/deb4be09060637ef4752adaa0b75bf770c20c823e8108705792f99cd4a6f/uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00", size = 4115980 }, +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410 }, + { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476 }, + { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855 }, + { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185 }, + { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256 }, + { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323 }, + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284 }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349 }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089 }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770 }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321 }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123 }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325 }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806 }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068 }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428 }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, ] [[package]] @@ -1627,58 +1740,62 @@ wheels = [ [[package]] name = "yarl" -version = "1.13.1" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/11/2b8334f4192646677a2e7da435670d043f536088af943ec242f31453e5ba/yarl-1.13.1.tar.gz", hash = "sha256:ec8cfe2295f3e5e44c51f57272afbd69414ae629ec7c6b27f5a410efc78b70a0", size = 165912 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/64/1eaa5d080ceb8742b75a25eff4d510439459ff9c7fbe03e8e929a732ca07/yarl-1.13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:216a6785f296169ed52cd7dcdc2612f82c20f8c9634bf7446327f50398732a51", size = 189609 }, - { url = "https://files.pythonhosted.org/packages/e2/49/7faf592dd5d4ae4b789988750739c327b81070aa6d428848ce71f6112c1b/yarl-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40c6e73c03a6befb85b72da213638b8aaa80fe4136ec8691560cf98b11b8ae6e", size = 115504 }, - { url = "https://files.pythonhosted.org/packages/0c/02/6dd48672009bdf135a298a7250875321098b7cbbca5af8c49d8dae07b635/yarl-1.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2430cf996113abe5aee387d39ee19529327205cda975d2b82c0e7e96e5fdabdc", size = 113754 }, - { url = "https://files.pythonhosted.org/packages/0e/4c/dd49a78833691ccdc15738eb814e37df47f0f25baeefb1cec64ecb4459eb/yarl-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fb4134cc6e005b99fa29dbc86f1ea0a298440ab6b07c6b3ee09232a3b48f495", size = 486101 }, - { url = "https://files.pythonhosted.org/packages/36/ec/e5e6ed4344de34d3554a22d181df4d90a4d0f257575c28b767ad8c1add0b/yarl-1.13.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309c104ecf67626c033845b860d31594a41343766a46fa58c3309c538a1e22b2", size = 505989 }, - { url = "https://files.pythonhosted.org/packages/7d/af/0318b0d03471207b3959e0e6ca2964b689744d8482fdbfdc2958854373b4/yarl-1.13.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f90575e9fe3aae2c1e686393a9689c724cd00045275407f71771ae5d690ccf38", size = 500428 }, - { url = "https://files.pythonhosted.org/packages/c4/09/5e47823e3abb26ddda447b500be28137971d246b0c771a02f855dd06b30b/yarl-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d2e1626be8712333a9f71270366f4a132f476ffbe83b689dd6dc0d114796c74", size = 488954 }, - { url = "https://files.pythonhosted.org/packages/9a/c4/e26317d48bd6bf59dfbb6049d022582a376de01440e5c2bbe92009f8117a/yarl-1.13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b66c87da3c6da8f8e8b648878903ca54589038a0b1e08dde2c86d9cd92d4ac9", size = 471561 }, - { url = "https://files.pythonhosted.org/packages/93/c5/4dfb00b84fc6df79b3e42d8716ba8f747d7ebf0c14640c7e65d923f39ea7/yarl-1.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cf1ad338620249f8dd6d4b6a91a69d1f265387df3697ad5dc996305cf6c26fb2", size = 485652 }, - { url = "https://files.pythonhosted.org/packages/9d/fb/bde1430c94d6e5de27d0031e3fb5d85467d975aecdc67e6c686f5c36bbfd/yarl-1.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9915300fe5a0aa663c01363db37e4ae8e7c15996ebe2c6cce995e7033ff6457f", size = 483530 }, - { url = "https://files.pythonhosted.org/packages/5c/80/9f9c9d567ac5fb355e252dc27b75ccf92a3e4bea8b1c5610d5d1240c1b30/yarl-1.13.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:703b0f584fcf157ef87816a3c0ff868e8c9f3c370009a8b23b56255885528f10", size = 514085 }, - { url = "https://files.pythonhosted.org/packages/aa/9b/3aeb817a60bde4be6acb476a46bc6184c27b5c91f23ec726d9e6e46b89cf/yarl-1.13.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1d8e3ca29f643dd121f264a7c89f329f0fcb2e4461833f02de6e39fef80f89da", size = 516342 }, - { url = "https://files.pythonhosted.org/packages/71/9d/d7aa4fd8b16e174c4c16b826f54a0e9e4533fb3ae09741906ccc811362d0/yarl-1.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7055bbade838d68af73aea13f8c86588e4bcc00c2235b4b6d6edb0dbd174e246", size = 498430 }, - { url = "https://files.pythonhosted.org/packages/b0/3d/b46aad1725f8d043beee2d47ffddffb1939178bec6f9584b46215efe5a78/yarl-1.13.1-cp311-cp311-win32.whl", hash = "sha256:a3442c31c11088e462d44a644a454d48110f0588de830921fd201060ff19612a", size = 102436 }, - { url = "https://files.pythonhosted.org/packages/89/9e/bbbda05279230dc12d879dfcf971f77f9c932e457fbcd870efb4c3bdf10c/yarl-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:81bad32c8f8b5897c909bf3468bf601f1b855d12f53b6af0271963ee67fff0d2", size = 111678 }, - { url = "https://files.pythonhosted.org/packages/64/de/1602352e5bb47c4b86921b004fe84d0646ef9abeda3dfc55f1d2271829e4/yarl-1.13.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f452cc1436151387d3d50533523291d5f77c6bc7913c116eb985304abdbd9ec9", size = 190253 }, - { url = "https://files.pythonhosted.org/packages/83/f0/2abc6f0af8f243c4a5190e687897e7684baea2c97f5f1be2321418163c7e/yarl-1.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9cec42a20eae8bebf81e9ce23fb0d0c729fc54cf00643eb251ce7c0215ad49fe", size = 116079 }, - { url = "https://files.pythonhosted.org/packages/ad/eb/a578f935e2b6834a00b38156f81f3a6545e14a360ff8a296019116502a9c/yarl-1.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d959fe96e5c2712c1876d69af0507d98f0b0e8d81bee14cfb3f6737470205419", size = 113943 }, - { url = "https://files.pythonhosted.org/packages/da/ee/2bf5f8ffbea5b18fbca274dd04e300a033e43e92d261ac60722361b216ce/yarl-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8c837ab90c455f3ea8e68bee143472ee87828bff19ba19776e16ff961425b57", size = 483984 }, - { url = "https://files.pythonhosted.org/packages/05/9f/20d07ed84cbac847b989ef61130f2cbec6dc60f273b81d51041c35740eb3/yarl-1.13.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94a993f976cdcb2dc1b855d8b89b792893220db8862d1a619efa7451817c836b", size = 499723 }, - { url = "https://files.pythonhosted.org/packages/e5/90/cc6d3dab4fc33b6f80d498c6276995fcbe16db1005141be6133345b597c1/yarl-1.13.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2442a415a5f4c55ced0fade7b72123210d579f7d950e0b5527fc598866e62c", size = 497279 }, - { url = "https://files.pythonhosted.org/packages/47/a0/c1404aa8c7e025aa05a81f3a34c42131f8b11836e49450e1558bcd64a3bb/yarl-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fdbf0418489525231723cdb6c79e7738b3cbacbaed2b750cb033e4ea208f220", size = 490188 }, - { url = "https://files.pythonhosted.org/packages/2e/8b/ebb195c4a4a5b5a84b0ade8469404609d68adf8f1dcf88e8b2b5297566cc/yarl-1.13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b7f6e699304717fdc265a7e1922561b02a93ceffdaefdc877acaf9b9f3080b8", size = 469378 }, - { url = "https://files.pythonhosted.org/packages/40/8f/6a00380c6653006ac0112ebbf0ff24eb7b2d71359ac2c410a98822d89bfa/yarl-1.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bcd5bf4132e6a8d3eb54b8d56885f3d3a38ecd7ecae8426ecf7d9673b270de43", size = 485681 }, - { url = "https://files.pythonhosted.org/packages/2c/94/797d18a3b9ea125a24ba3c69cd71b3561d227d5bb61dbadf2cb2afd6c319/yarl-1.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2a93a4557f7fc74a38ca5a404abb443a242217b91cd0c4840b1ebedaad8919d4", size = 486049 }, - { url = "https://files.pythonhosted.org/packages/75/b2/3573e18eb52ca204ee076a94c145edc80c3df21694648b35ae34c19ac9bb/yarl-1.13.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:22b739f99c7e4787922903f27a892744189482125cc7b95b747f04dd5c83aa9f", size = 506742 }, - { url = "https://files.pythonhosted.org/packages/1f/36/f6b5b0fb7c771d5c6c08b7d00a53cd523793454113d4c96460e3f49a1cdd/yarl-1.13.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2db874dd1d22d4c2c657807562411ffdfabec38ce4c5ce48b4c654be552759dc", size = 517070 }, - { url = "https://files.pythonhosted.org/packages/8e/17/48637d4ddcb606f5591afee78d060eab70e172e14766e1fd23453bfed846/yarl-1.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4feaaa4742517eaceafcbe74595ed335a494c84634d33961214b278126ec1485", size = 502397 }, - { url = "https://files.pythonhosted.org/packages/83/2c/7392645dc1c9eeb8a5485696302a33e3d59bea8a448c8e2f36f98a728e0a/yarl-1.13.1-cp312-cp312-win32.whl", hash = "sha256:bbf9c2a589be7414ac4a534d54e4517d03f1cbb142c0041191b729c2fa23f320", size = 102343 }, - { url = "https://files.pythonhosted.org/packages/9c/c0/7329799080d7e0bf7b10db417900701ba6810e78a249aef1f4bf3fc2cccb/yarl-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:d07b52c8c450f9366c34aa205754355e933922c79135125541daae6cbf31c799", size = 111719 }, - { url = "https://files.pythonhosted.org/packages/d3/d2/9542e6207a6e64c32b14b2d9ca4fad6ff80310fc75e70cdbe31680a758c2/yarl-1.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:95c6737f28069153c399d875317f226bbdea939fd48a6349a3b03da6829fb550", size = 186266 }, - { url = "https://files.pythonhosted.org/packages/8b/68/4c6d1aacbc23a05e84c3fab7aaa68c5a7d4531290021c2370fa1e5524fb1/yarl-1.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cd66152561632ed4b2a9192e7f8e5a1d41e28f58120b4761622e0355f0fe034c", size = 114268 }, - { url = "https://files.pythonhosted.org/packages/ed/87/6ad8e22c918d745092329ec427c0778b5c85ffd5b805e38750024b7464f2/yarl-1.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6a2acde25be0cf9be23a8f6cbd31734536a264723fca860af3ae5e89d771cd71", size = 112164 }, - { url = "https://files.pythonhosted.org/packages/ca/5b/c6c4ac4be1edea6759f05ad74d87a1c61329737bdb90da5f66e188310461/yarl-1.13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18595e6a2ee0826bf7dfdee823b6ab55c9b70e8f80f8b77c37e694288f5de1", size = 471437 }, - { url = "https://files.pythonhosted.org/packages/c1/5c/ec7f0121a5fa67ee76325e1aaa27470d5521d80a25aa1bad5dde773edbe1/yarl-1.13.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a31d21089894942f7d9a8df166b495101b7258ff11ae0abec58e32daf8088813", size = 485894 }, - { url = "https://files.pythonhosted.org/packages/d7/e8/624fc8082cbff62c537798ce837a6044f70e2e00472ab719deb376ff6e39/yarl-1.13.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45f209fb4bbfe8630e3d2e2052535ca5b53d4ce2d2026bed4d0637b0416830da", size = 486702 }, - { url = "https://files.pythonhosted.org/packages/dc/18/013f7d2e3f0ff28b85299ed19164f899ea4f02da8812621a40937428bf48/yarl-1.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f722f30366474a99745533cc4015b1781ee54b08de73260b2bbe13316079851", size = 478911 }, - { url = "https://files.pythonhosted.org/packages/d7/3c/5b628939e3a22fb9375df453188e97190d21f6244c49637e19799896cd41/yarl-1.13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3bf60444269345d712838bb11cc4eadaf51ff1a364ae39ce87a5ca8ad3bb2c8", size = 456488 }, - { url = "https://files.pythonhosted.org/packages/8b/2b/a3548db86510c1d95bff344c1c588b84582eeb3a55ea15a149a24d7069f0/yarl-1.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:942c80a832a79c3707cca46bd12ab8aa58fddb34b1626d42b05aa8f0bcefc206", size = 475016 }, - { url = "https://files.pythonhosted.org/packages/d8/e2/e2a540f18f849909e3ee594766bf7b0a7fde176ff0cfb2f95121033752e2/yarl-1.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:44b07e1690f010c3c01d353b5790ec73b2f59b4eae5b0000593199766b3f7a5c", size = 477521 }, - { url = "https://files.pythonhosted.org/packages/3a/df/4cda4052da48a57ce4f20a0849b7344902aa3e149a0b409525509fc43985/yarl-1.13.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:396e59b8de7e4d59ff5507fb4322d2329865b909f29a7ed7ca37e63ade7f835c", size = 492000 }, - { url = "https://files.pythonhosted.org/packages/bf/b6/180dbb0aa846cafb9ce89bd33c477e200dd00072c7775372f34651c20b9a/yarl-1.13.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3bb83a0f12701c0b91112a11148b5217617982e1e466069d0555be9b372f2734", size = 502195 }, - { url = "https://files.pythonhosted.org/packages/ff/37/e97c280344342e326a1860a70054a0488c379e8937325f97f9a9fe6b453d/yarl-1.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c92b89bffc660f1274779cb6fbb290ec1f90d6dfe14492523a0667f10170de26", size = 492892 }, - { url = "https://files.pythonhosted.org/packages/ed/97/cd35f39ba8183ef193a6709aa0b2fcaabebd6915202d6999b01fa630b2bb/yarl-1.13.1-cp313-cp313-win32.whl", hash = "sha256:269c201bbc01d2cbba5b86997a1e0f73ba5e2f471cfa6e226bcaa7fd664b598d", size = 486463 }, - { url = "https://files.pythonhosted.org/packages/05/33/bd9d33503a0f73d095b01ed438423b924e6786e90102ca4912e573cc5aa3/yarl-1.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:1d0828e17fa701b557c6eaed5edbd9098eb62d8838344486248489ff233998b8", size = 493804 }, - { url = "https://files.pythonhosted.org/packages/74/81/419c24f7c94f56b96d04955482efb5b381635ad265b5b7fbab333a9dfde3/yarl-1.13.1-py3-none-any.whl", hash = "sha256:6a5185ad722ab4dd52d5fb1f30dcc73282eb1ed494906a92d1a228d3f89607b0", size = 39862 }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/9c/9c0a9bfa683fc1be7fdcd9687635151544d992cccd48892dc5e0a5885a29/yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47", size = 178163 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/0f/ce6a2c8aab9946446fb27f1e28f0fd89ce84ae913ab18a92d18078a1c7ed/yarl-1.17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217", size = 140727 }, + { url = "https://files.pythonhosted.org/packages/9d/df/204f7a502bdc3973cd9fc29e7dfad18ae48b3acafdaaf1ae07c0f41025aa/yarl-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988", size = 93560 }, + { url = "https://files.pythonhosted.org/packages/a2/e1/f4d522ae0560c91a4ea31113a50f00f85083be885e1092fc6e74eb43cb1d/yarl-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75", size = 91497 }, + { url = "https://files.pythonhosted.org/packages/f1/82/783d97bf4a226f1a2e59b1966f2752244c2bf4dc89bc36f61d597b8e34e5/yarl-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca", size = 339446 }, + { url = "https://files.pythonhosted.org/packages/e5/ff/615600647048d81289c80907165de713fbc566d1e024789863a2f6563ba3/yarl-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74", size = 354616 }, + { url = "https://files.pythonhosted.org/packages/a5/04/bfb7adb452bd19dfe0c35354ffce8ebc3086e028e5f8270e409d17da5466/yarl-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f", size = 351801 }, + { url = "https://files.pythonhosted.org/packages/10/e0/efe21edacdc4a638ce911f8cabf1c77cac3f60e9819ba7d891b9ceb6e1d4/yarl-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d", size = 343381 }, + { url = "https://files.pythonhosted.org/packages/63/f9/7bc7e69857d6fc3920ecd173592f921d5701f4a0dd3f2ae293b386cfa3bf/yarl-1.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11", size = 337093 }, + { url = "https://files.pythonhosted.org/packages/93/52/99da61947466275ff17d7bc04b0ac31dfb7ec699bd8d8985dffc34c3a913/yarl-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0", size = 346619 }, + { url = "https://files.pythonhosted.org/packages/91/8a/8aaad86a35a16e485ba0e5de0d2ae55bf8dd0c9f1cccac12be4c91366b1d/yarl-1.17.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3", size = 344347 }, + { url = "https://files.pythonhosted.org/packages/af/b6/97f29f626b4a1768ffc4b9b489533612cfcb8905c90f745aade7b2eaf75e/yarl-1.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe", size = 350316 }, + { url = "https://files.pythonhosted.org/packages/d7/98/8e0e8b812479569bdc34d66dd3e2471176ca33be4ff5c272a01333c4b269/yarl-1.17.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860", size = 361336 }, + { url = "https://files.pythonhosted.org/packages/9e/d3/d1507efa0a85c25285f8eb51df9afa1ba1b6e446dda781d074d775b6a9af/yarl-1.17.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4", size = 365350 }, + { url = "https://files.pythonhosted.org/packages/22/ba/ee7f1830449c96bae6f33210b7d89e8aaf3079fbdaf78ac398e50a9da404/yarl-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4", size = 357689 }, + { url = "https://files.pythonhosted.org/packages/a0/85/321c563dc5afe1661108831b965c512d185c61785400f5606006507d2e18/yarl-1.17.1-cp311-cp311-win32.whl", hash = "sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7", size = 83635 }, + { url = "https://files.pythonhosted.org/packages/bc/da/543a32c00860588ff1235315b68f858cea30769099c32cd22b7bb266411b/yarl-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3", size = 90218 }, + { url = "https://files.pythonhosted.org/packages/5d/af/e25615c7920396219b943b9ff8b34636ae3e1ad30777649371317d7f05f8/yarl-1.17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61", size = 141839 }, + { url = "https://files.pythonhosted.org/packages/83/5e/363d9de3495c7c66592523f05d21576a811015579e0c87dd38c7b5788afd/yarl-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d", size = 94125 }, + { url = "https://files.pythonhosted.org/packages/e3/a2/b65447626227ebe36f18f63ac551790068bf42c69bb22dfa3ae986170728/yarl-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139", size = 92048 }, + { url = "https://files.pythonhosted.org/packages/a1/f5/2ef86458446f85cde10582054fd5113495ef8ce8477da35aaaf26d2970ef/yarl-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5", size = 331472 }, + { url = "https://files.pythonhosted.org/packages/f3/6b/1ba79758ba352cdf2ad4c20cab1b982dd369aa595bb0d7601fc89bf82bee/yarl-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac", size = 341260 }, + { url = "https://files.pythonhosted.org/packages/2d/41/4e07c2afca3f9ed3da5b0e38d43d0280d9b624a3d5c478c425e5ce17775c/yarl-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463", size = 340882 }, + { url = "https://files.pythonhosted.org/packages/c3/c0/cd8e94618983c1b811af082e1a7ad7764edb3a6af2bc6b468e0e686238ba/yarl-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147", size = 336648 }, + { url = "https://files.pythonhosted.org/packages/ac/fc/73ec4340d391ffbb8f34eb4c55429784ec9f5bd37973ce86d52d67135418/yarl-1.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7", size = 325019 }, + { url = "https://files.pythonhosted.org/packages/57/48/da3ebf418fc239d0a156b3bdec6b17a5446f8d2dea752299c6e47b143a85/yarl-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685", size = 342841 }, + { url = "https://files.pythonhosted.org/packages/5d/79/107272745a470a8167924e353a5312eb52b5a9bb58e22686adc46c94f7ec/yarl-1.17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172", size = 341433 }, + { url = "https://files.pythonhosted.org/packages/30/9c/6459668b3b8dcc11cd061fc53e12737e740fb6b1575b49c84cbffb387b3a/yarl-1.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7", size = 344927 }, + { url = "https://files.pythonhosted.org/packages/c5/0b/93a17ed733aca8164fc3a01cb7d47b3f08854ce4f957cce67a6afdb388a0/yarl-1.17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da", size = 355732 }, + { url = "https://files.pythonhosted.org/packages/9a/63/ead2ed6aec3c59397e135cadc66572330325a0c24cd353cd5c94f5e63463/yarl-1.17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c", size = 362123 }, + { url = "https://files.pythonhosted.org/packages/89/bf/f6b75b4c2fcf0e7bb56edc0ed74e33f37fac45dc40e5a52a3be66b02587a/yarl-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199", size = 356355 }, + { url = "https://files.pythonhosted.org/packages/45/1f/50a0257cd07eef65c8c65ad6a21f5fb230012d659e021aeb6ac8a7897bf6/yarl-1.17.1-cp312-cp312-win32.whl", hash = "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96", size = 83279 }, + { url = "https://files.pythonhosted.org/packages/bc/82/fafb2c1268d63d54ec08b3a254fbe51f4ef098211501df646026717abee3/yarl-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df", size = 89590 }, + { url = "https://files.pythonhosted.org/packages/06/1e/5a93e3743c20eefbc68bd89334d9c9f04f3f2334380f7bbf5e950f29511b/yarl-1.17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488", size = 139974 }, + { url = "https://files.pythonhosted.org/packages/a1/be/4e0f6919013c7c5eaea5c31811c551ccd599d2fc80aa3dd6962f1bbdcddd/yarl-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374", size = 93364 }, + { url = "https://files.pythonhosted.org/packages/73/f0/650f994bc491d0cb85df8bb45392780b90eab1e175f103a5edc61445ff67/yarl-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac", size = 91177 }, + { url = "https://files.pythonhosted.org/packages/f3/e8/9945ed555d14b43ede3ae8b1bd73e31068a694cad2b9d3cad0a28486c2eb/yarl-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170", size = 333086 }, + { url = "https://files.pythonhosted.org/packages/a6/c0/7d167e48e14d26639ca066825af8da7df1d2fcdba827e3fd6341aaf22a3b/yarl-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8", size = 343661 }, + { url = "https://files.pythonhosted.org/packages/fa/81/80a266517531d4e3553aecd141800dbf48d02e23ebd52909e63598a80134/yarl-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938", size = 345196 }, + { url = "https://files.pythonhosted.org/packages/b0/77/6adc482ba7f2dc6c0d9b3b492e7cd100edfac4cfc3849c7ffa26fd7beb1a/yarl-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e", size = 338743 }, + { url = "https://files.pythonhosted.org/packages/6d/cc/f0c4c0b92ff3ada517ffde2b127406c001504b225692216d969879ada89a/yarl-1.17.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556", size = 326719 }, + { url = "https://files.pythonhosted.org/packages/18/3b/7bfc80d3376b5fa162189993a87a5a6a58057f88315bd0ea00610055b57a/yarl-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67", size = 345826 }, + { url = "https://files.pythonhosted.org/packages/2e/66/cf0b0338107a5c370205c1a572432af08f36ca12ecce127f5b558398b4fd/yarl-1.17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8", size = 340335 }, + { url = "https://files.pythonhosted.org/packages/2f/52/b084b0eec0fd4d2490e1d33ace3320fad704c5f1f3deaa709f929d2d87fc/yarl-1.17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3", size = 345301 }, + { url = "https://files.pythonhosted.org/packages/ef/38/9e2036d948efd3bafcdb4976cb212166fded76615f0dfc6c1492c4ce4784/yarl-1.17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0", size = 354205 }, + { url = "https://files.pythonhosted.org/packages/81/c1/13dfe1e70b86811733316221c696580725ceb1c46d4e4db852807e134310/yarl-1.17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299", size = 360501 }, + { url = "https://files.pythonhosted.org/packages/91/87/756e05c74cd8bf9e71537df4a2cae7e8211a9ebe0d2350a3e26949e1e41c/yarl-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258", size = 359452 }, + { url = "https://files.pythonhosted.org/packages/06/b2/b2bb09c1e6d59e1c9b1b36a86caa473e22c3dbf26d1032c030e9bfb554dc/yarl-1.17.1-cp313-cp313-win32.whl", hash = "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2", size = 308904 }, + { url = "https://files.pythonhosted.org/packages/f3/27/f084d9a5668853c1f3b246620269b14ee871ef3c3cc4f3a1dd53645b68ec/yarl-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda", size = 314637 }, + { url = "https://files.pythonhosted.org/packages/52/ad/1fe7ff5f3e8869d4c5070f47b96bac2b4d15e67c100a8278d8e7876329fc/yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06", size = 44352 }, ] diff --git a/examples/python/python-02-simple-chatbot/uv.lock b/examples/python/python-02-simple-chatbot/uv.lock index fc0755a8..dff9cd38 100644 --- a/examples/python/python-02-simple-chatbot/uv.lock +++ b/examples/python/python-02-simple-chatbot/uv.lock @@ -343,24 +343,6 @@ requires-dist = [ { name = "semantic-workbench-assistant", editable = "../../../libraries/python/semantic-workbench-assistant" }, ] -[[package]] -name = "context" -version = "0.1.0" -source = { editable = "../../../libraries/python/context" } -dependencies = [ - { name = "events" }, -] - -[package.metadata] -requires-dist = [{ name = "events", editable = "../../../libraries/python/events" }] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "cryptography" version = "43.0.1" @@ -522,40 +504,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../../libraries/python/function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../libraries/python/context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -847,7 +795,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -860,7 +808,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../../libraries/python/function-registry" }, + { name = "events", editable = "../../../libraries/python/events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, diff --git a/examples/python/python-03-multimodel-chatbot/uv.lock b/examples/python/python-03-multimodel-chatbot/uv.lock index fe7e3750..b7bc1098 100644 --- a/examples/python/python-03-multimodel-chatbot/uv.lock +++ b/examples/python/python-03-multimodel-chatbot/uv.lock @@ -375,24 +375,6 @@ requires-dist = [ { name = "semantic-workbench-assistant", editable = "../../../libraries/python/semantic-workbench-assistant" }, ] -[[package]] -name = "context" -version = "0.1.0" -source = { editable = "../../../libraries/python/context" } -dependencies = [ - { name = "events" }, -] - -[package.metadata] -requires-dist = [{ name = "events", editable = "../../../libraries/python/events" }] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "cryptography" version = "43.0.1" @@ -572,40 +554,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/a0/6aaea0c2fbea2f89bfd5db25fb1e3481896a423002ebe4e55288907a97a3/fsspec-2024.9.0-py3-none-any.whl", hash = "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b", size = 179253 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../../libraries/python/function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../libraries/python/context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "google-ai-generativelanguage" version = "0.6.10" @@ -1086,7 +1034,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -1099,7 +1047,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../../libraries/python/function-registry" }, + { name = "events", editable = "../../../libraries/python/events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, diff --git a/libraries/python/chat-driver/chat_driver/__init__.py b/libraries/python/chat-driver/chat_driver/__init__.py deleted file mode 100644 index 76bbf0de..00000000 --- a/libraries/python/chat-driver/chat_driver/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# As a convenience, allow users to import the Context and ContextProtocol from -# the chat_driver package. -import logging - -from context import Context, ContextProtocol -from openai_client.completion import JSON_OBJECT_RESPONSE_FORMAT, TEXT_RESPONSE_FORMAT, ResponseFormat - -from .chat_driver import ( - ChatDriver, - ChatDriverConfig, -) -from .local_message_history_provider import LocalMessageHistoryProvider, LocalMessageHistoryProviderConfig -from .message_history_provider import MessageHistoryProviderProtocol - -logger = logging.getLogger(__name__) - -__all__ = [ - "ChatDriver", - "ChatDriverConfig", - "Context", - "ContextProtocol", - "JSON_OBJECT_RESPONSE_FORMAT", - "LocalMessageHistoryProvider", - "LocalMessageHistoryProviderConfig", - "MessageHistoryProviderProtocol", - "ResponseFormat", - "TEXT_RESPONSE_FORMAT", -] diff --git a/libraries/python/chat-driver/pyproject.toml b/libraries/python/chat-driver/pyproject.toml deleted file mode 100644 index 4a8832b1..00000000 --- a/libraries/python/chat-driver/pyproject.toml +++ /dev/null @@ -1,33 +0,0 @@ -[project] -name = "chat-driver" -version = "0.1.0" -description = "MADE:Exploration Chat Driver" -authors = [{name="MADE:Explorers"}] -readme = "README.md" -requires-python = ">=3.11" -dependencies = [ - "azure-identity>=1.17.1", - "context>=0.1.0", - "events>=0.1.0", - "function-registry>=0.1.0", - "openai>=1.16.1", - "openai-client>=0.1.0", - "pydantic-settings>=2.3.4", - "pydantic>=2.6.1", - "python-dotenv>=1.0.1", - "requests>=2.32.0", - "tiktoken>=0.7.0", -] - -[tool.uv] -package = true - -[tool.uv.sources] -context = { path = "../context", editable = true } -events = { path = "../events", editable = true } -function-registry = { path = "../function-registry", editable = true } -openai-client = { path = "../openai-client", editable = true } - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" diff --git a/libraries/python/context/README.md b/libraries/python/context/README.md index 66f5ca23..f5e6580a 100644 --- a/libraries/python/context/README.md +++ b/libraries/python/context/README.md @@ -3,7 +3,6 @@ This library is shared among several different Make:Exploration projects including: - [Skill Library](../skills/skill-library/README.md) -- [Chat Driver](../chat-driver/README.md) The [Context](./context/context.py) class in this library is designed to be instantiated once and passed to all relevant parts of a system. diff --git a/libraries/python/context/context/context.py b/libraries/python/context/context/context.py index 256199b7..3bde2d04 100644 --- a/libraries/python/context/context/context.py +++ b/libraries/python/context/context/context.py @@ -27,7 +27,7 @@ class ContextProtocol(Protocol): # The emit function is used to send events to the event bus. The component # that creates this context object will be responsible for instantiating an # event bus and handling the events sent to it with this function. - emit: Optional[Callable[[EventProtocol], None]] + emit: Callable[[EventProtocol], None] class LogEmitter: @@ -38,7 +38,7 @@ def emit(self, event: EventProtocol) -> None: logging.info(event.to_json()) -class Context: +class Context(ContextProtocol): """A default context object that implements the ContextProtocol. The context object that is passed to all components (functions, action, routines, etc.). The context object is used to pass information between these components, @@ -56,7 +56,11 @@ def __init__( self.emit = emit or LogEmitter().emit def to_dict(self) -> dict[str, Any]: - return {"session_id": self.session_id, "run_id": self.run_id, "emit": self.emit.__class__.__name__} + return { + "session_id": self.session_id, + "run_id": self.run_id, + "emit": self.emit.__class__.__name__, + } def __repr__(self) -> str: return f"Context({self.session_id})" diff --git a/libraries/python/events/events/events.py b/libraries/python/events/events/events.py index dbe84b46..80ba9075 100644 --- a/libraries/python/events/events/events.py +++ b/libraries/python/events/events/events.py @@ -38,7 +38,7 @@ class BaseEvent(BaseModel): session_id: str | None = Field(default=None) timestamp: datetime = Field(default_factory=datetime.now) message: str | None = Field(default=None) - metadata: dict[str, Any] = {} + metadata: dict[str, Any] = Field(default_factory=dict) def __str__(self) -> str: return f"{self.__class__.__name__}: {self.message}" diff --git a/libraries/python/function-registry/.vscode/extensions.json b/libraries/python/function-registry/.vscode/extensions.json deleted file mode 100644 index 3b812215..00000000 --- a/libraries/python/function-registry/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "recommendations": [ - "aarontamasfe.even-better-toml", - "charliermarsh.ruff", - "ms-python.python" - ] -} diff --git a/libraries/python/function-registry/.vscode/settings.json b/libraries/python/function-registry/.vscode/settings.json deleted file mode 100644 index e930c1dc..00000000 --- a/libraries/python/function-registry/.vscode/settings.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "editor.bracketPairColorization.enabled": true, - "editor.codeActionsOnSave": { - "source.fixAll": "always", - "source.organizeImports": "always" - }, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnPaste": true, - "editor.formatOnSave": true, - "editor.formatOnType": true, - "editor.guides.bracketPairs": "active", - "files.eol": "\n", - "files.trimTrailingWhitespace": true, - "flake8.ignorePatterns": ["**/*.py"], // disable flake8 in favor of ruff - "jupyter.debugJustMyCode": false, - "python.analysis.autoFormatStrings": true, - "python.analysis.autoImportCompletions": true, - "python.analysis.diagnosticMode": "workspace", - "python.analysis.fixAll": ["source.unusedImports"], - "python.analysis.inlayHints.functionReturnTypes": true, - "python.analysis.typeCheckingMode": "basic", - "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", - "python.testing.cwd": "${workspaceFolder}", - "python.testing.pytestArgs": ["--color", "yes"], - "python.testing.pytestEnabled": true, - "search.exclude": { - "**/.venv": true, - "**/data": true - }, - "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "[jsonc]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "[python]": { - "editor.defaultFormatter": "charliermarsh.ruff", - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll": "explicit", - "source.unusedImports": "explicit", - "source.organizeImports": "explicit", - "source.formatDocument": "explicit" - } - }, - "ruff.nativeServer": "on", - "cSpell.words": [ - "dotenv", - "elts", - "httpx", - "jsonschema", - "openai", - "pydantic", - "pypdf", - "runtimes", - "tiktoken" - ] -} diff --git a/libraries/python/function-registry/Makefile b/libraries/python/function-registry/Makefile deleted file mode 100644 index 1ad4520a..00000000 --- a/libraries/python/function-registry/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -repo_root = $(shell git rev-parse --show-toplevel) -include $(repo_root)/tools/makefiles/python.mk diff --git a/libraries/python/function-registry/README.md b/libraries/python/function-registry/README.md deleted file mode 100644 index 4ad6fe6a..00000000 --- a/libraries/python/function-registry/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Function Registry - -This library contains classes and utilities helpful for managing the registration of functions to [chat drivers](../chat-driver/README.md) and [skills](../skills/skill-library/README.md). - -These other components generally need utilities such as generating JSON-Schema -of functions, converting strings into function calls, -[context](../context/README.md) passing. These are all generally useful -functions to have when working with "agentic systems" (systems using language -models). diff --git a/libraries/python/function-registry/function_registry/__init__.py b/libraries/python/function-registry/function_registry/__init__.py deleted file mode 100644 index 78f6e4a1..00000000 --- a/libraries/python/function-registry/function_registry/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .function_registry import FunctionRegistry - -__all__ = ["FunctionRegistry"] diff --git a/libraries/python/function-registry/function_registry/function.py b/libraries/python/function-registry/function_registry/function.py deleted file mode 100644 index 39bdbbb7..00000000 --- a/libraries/python/function-registry/function_registry/function.py +++ /dev/null @@ -1,122 +0,0 @@ -import inspect -from dataclasses import dataclass -from typing import Any, Callable - -from context.context import ContextProtocol -from pydantic import create_model -from pydantic.fields import FieldInfo - - -@dataclass -class Parameter: - name: str - type: Any - description: str | None - default_value: Any | None = None - - -class Function: - def __init__( - self, name: str, description: str | None, parameters: list[Parameter], fn: Callable, strict_schema: bool = False - ) -> None: - self.name = name - self.description = description or name.replace("_", " ").title() - self.parameters = parameters - self.fn = fn - self.schema: dict[str, Any] = self._generate_schema(strict=strict_schema) - self.usage: str = self._generate_usage() - - async def execute(self, context: ContextProtocol, *args, **kwargs) -> Any: - result = self.fn(context, *args, **kwargs) - if inspect.iscoroutine(result): - return await result - return result - - def _generate_usage(self) -> str: - """A usage string for this function.""" - name = self.name - param_usages = [] - for param in self.parameters: - param_type = param.type - try: - param_type = param.type.__name__ - except AttributeError: - param_type = param.type - usage = f"{param.name}: {param_type}" - if param.default_value is not inspect.Parameter.empty: - if isinstance(param.default_value, str): - usage += f' = "{param.default_value}"' - else: - usage += f" = {param.default_value}" - param_usages.append(usage) - - description = self.description - return f"{name}({', '.join(param_usages)}): {description}" - - def _generate_schema(self, strict: bool = True) -> dict[str, Any]: - """ - Generate a JSON schema for a function based on its signature. - """ - - # Create the Pydantic model using create_model. - - model_name = self.fn.__name__.title().replace("_", "") - fields = {} - for parameter in self.parameters: - field_info = FieldInfo(description=parameter.description) - if parameter.default_value is not inspect.Parameter.empty: - field_info.default = parameter.default_value - fields[parameter.name] = ( - parameter.type, - field_info, - ) - pydantic_model = create_model(model_name, **fields) - # pydantic_model.model_config = {"field_title_generator": lambda _, _: None} - - # Generate the JSON schema from the Pydantic model. - parameters_schema = pydantic_model.model_json_schema(mode="serialization") - - # Remove title attribute from all properties. - properties = parameters_schema["properties"] - for property_key in properties.keys(): - if "title" in properties[property_key]: - del properties[property_key]["title"] - - # And from the top-level object. - if "title" in parameters_schema: - del parameters_schema["title"] - - name = self.fn.__name__ - description = inspect.getdoc(self.fn) or name.replace("_", " ").title() - - # Output a schema that matches OpenAI's "tool" format. - # e.g., https://platform.openai.com/docs/guides/function-calling - # We use this because they trained GPT on it. - schema = { - # "$schema": "http://json-schema.org/draft-07/schema#", - # "$id": f"urn:jsonschema:{name}", - "name": name, - "description": description, - "strict": strict, - "parameters": { - "type": "object", - "properties": parameters_schema["properties"], - }, - } - - # If this is a strict schema requirement, OpenAI requires - # additionalProperties to be False. - # if strict: - schema["parameters"]["additionalProperties"] = False - - # Add required fields. - if "required" in parameters_schema: - schema["parameters"]["required"] = parameters_schema["required"] - - # Add definitions. - if "$defs" in parameters_schema: - schema["parameters"]["$defs"] = parameters_schema["$defs"] - for key in schema["parameters"]["$defs"]: - schema["parameters"]["$defs"][key]["additionalProperties"] = False - - return schema diff --git a/libraries/python/function-registry/function_registry/function_registry.py b/libraries/python/function-registry/function_registry/function_registry.py deleted file mode 100644 index 91459938..00000000 --- a/libraries/python/function-registry/function_registry/function_registry.py +++ /dev/null @@ -1,217 +0,0 @@ -import ast -import inspect -import json -import logging -from typing import Any, Callable - -from context.context import ContextProtocol -from pydantic import BaseModel - -from .function import Function, Parameter - - -class FunctionHandler: - def __init__(self, registry: "FunctionRegistry"): - self.registry = registry - - def __getattr__(self, name: str) -> Callable: - """Makes registered functions accessible as attributes of the functions object.""" - if name not in self.registry.function_map: - raise AttributeError(f"'FunctionHandler' object has no attribute '{name}'") - - async def wrapper(*args, **kwargs) -> Any: - return await self.registry.execute_function(name, args, kwargs) - - return wrapper - - -class FunctionRegistry: - """A function registry helps manage a collection of functions that can be - executed. Functions can be supplied on initialization or registered later. - Additional utilities are provided, such as generating JSON schemas and - returning results as strings.""" - - def __init__(self, context: ContextProtocol, functions: list[Callable] = []) -> None: - self.context = context - - self.function_map: dict[str, Function] = {} - - # By default, every registry has a help function. - self.register_function(self.help, strict_schema=True) - - # But the help function can be overridden if you give it another one. - self.register_functions(functions, strict_schema=True) - - # This allows actions to be called as attributes. - self.functions = FunctionHandler(self) - - def help(self, context: ContextProtocol) -> str: - """Return this help message.""" - return "Commands:\n" + "\n".join([f"{command.usage}" for command in self.function_map.values()]) - - def get_function(self, name: str) -> Function | None: - return self.function_map.get(name) - - def list_functions(self) -> list[str]: - return list(self.function_map.keys()) - - def get_functions(self) -> list[Function]: - return [function for function in self.function_map.values()] - - def has_function(self, name: str) -> bool: - if name == "help": - return True - return name in self.function_map - - def register_function(self, function: Callable, strict_schema: bool = True) -> None: - # Ensure the first argument of the function is the context. - if not inspect.signature(function).parameters: - raise ValueError(f"Function {function.__name__} must have at least one parameter (context: Context).") - if list(inspect.signature(function).parameters.keys())[0] != "context": - raise ValueError(f"Function `{function.__name__}` must have `context` as its first parameter.") - - # Get the function's parameters and their default values. - parameters = dict(inspect.signature(function).parameters) - if "context" in parameters: - del parameters["context"] - params = [ - Parameter( - name=param_name, - type=param.annotation, - description=None, # param.annotation.description, - default_value=param.default, - ) - for param_name, param in parameters.items() - ] - - self.function_map[function.__name__] = Function( - name=function.__name__, - description=inspect.getdoc(function) or function.__name__.replace("_", " ").title(), - fn=function, - parameters=params, - strict_schema=strict_schema, - ) - - def register_functions(self, functions: list[Callable], strict_schema: bool = True) -> None: - for function in functions: - if function.__name__ in self.function_map: - logging.warning(f"Function {function.__name__} already registered.") - continue - if not callable(function): - raise ValueError(f"Function {function} is not callable.") - self.register_function(function, strict_schema=strict_schema) - - async def execute_function(self, name: str, args: tuple, kwargs: dict[str, Any]) -> Any: - """Run a registered function by name. Passes the context as the first argument.""" - function = self.get_function(name) - if not function: - raise ValueError(f"Function {name} not found in registry.") - return await function.execute(self.context, *args, **kwargs) - - async def execute_function_with_string_response(self, name: str, args: tuple, kwargs: dict[str, Any]) -> str: - """Run a registered function by name and return a string response.""" - try: - result = await self.execute_function(name, args, kwargs) - return __class__.to_string_response(result) - except Exception as e: - return f"Error running function {name}: {e}" - - async def execute_function_string(self, function_string: str) -> Any: - """Parse a function string and execute the function.""" - try: - function, args, kwargs = self.parse_function_string(function_string) - except ValueError as e: - raise ValueError(f"{e}. Type: `/help` for more information.") - if not function: - raise ValueError("Function not found in registry. Type: `/help` for more information.") - return await function.execute(self.context, *args, **kwargs) - - async def execute_function_string_with_string_response(self, function_string: str) -> str: - """Parse a function string and execute the function, returning a string response.""" - try: - result = await self.execute_function_string(function_string) - return __class__.to_string_response(result) - except Exception as e: - return f"Error running function: {e}" - - def parse_function_string(self, function_string) -> tuple[Function | None, list[Any], dict[str, Any]]: - """Parse a function call string into a function and its arguments.""" - # As a convenience, remove any leading slashes. - function_string = function_string.lstrip("/") - - # As a convenience, add parentheses if they are missing. - if " " not in function_string and "(" not in function_string: - function_string += "()" - - # Parse the string into an AST (Abstract Syntax Tree) - try: - tree = ast.parse(function_string) - except SyntaxError: - raise ValueError("Invalid function call. Please check your syntax.") - - # Ensure the tree contains exactly one expression (the function call) - if not (isinstance(tree, ast.Module) and len(tree.body) == 1 and isinstance(tree.body[0], ast.Expr)): - raise ValueError("Expected a single function call.") - - # The function call is stored as a `Call` node within the expression - call_node = tree.body[0].value - if not isinstance(call_node, ast.Call): - raise ValueError("Invalid function call. Please check your syntax.") - - # Extract the function name - if isinstance(call_node.func, ast.Name): - function_name = call_node.func.id - else: - raise ValueError("Unsupported function format. Please check your syntax.") - - # Helper function to evaluate AST nodes to their Python equivalent - def eval_node(node): - if isinstance(node, ast.Constant): - return node.value - elif isinstance(node, ast.List): - return [eval_node(elem) for elem in node.elts] - elif isinstance(node, ast.Tuple): - return tuple(eval_node(elem) for elem in node.elts) - elif isinstance(node, ast.Dict): - return {eval_node(key): eval_node(value) for key, value in zip(node.keys, node.values)} - elif isinstance(node, ast.Name): - return node.id # This can return variable names, but we assume they're constants - elif isinstance(node, ast.BinOp): # Handling arithmetic expressions - return eval(compile(ast.Expression(node), filename="", mode="eval")) - elif isinstance(node, ast.Call): - raise ValueError("Nested function calls are not supported.") - else: - raise ValueError(f"Unsupported AST node type: {type(node).__name__}") - - # Extract positional arguments - args = [eval_node(arg) for arg in call_node.args] - - # Extract keyword arguments - kwargs = {} - for kw in call_node.keywords: - kwargs[kw.arg] = eval_node(kw.value) - - function = self.get_function(function_name) - if not function: - return None, [], {} - - return function, args, kwargs - - @staticmethod - def to_string_response(result: Any) -> str: - if result is None: - return "Function executed successfully." - elif isinstance(result, str): - return result - elif isinstance(result, (int, float)): - return str(result) - elif isinstance(result, dict): - return json.dumps(result) - elif isinstance(result, list): - return json.dumps(result, indent=2) - elif isinstance(result, tuple): - return json.dumps(result) - elif isinstance(result, BaseModel): - return result.model_dump_json(indent=2) - else: - return str(result) diff --git a/libraries/python/function-registry/pyproject.toml b/libraries/python/function-registry/pyproject.toml deleted file mode 100644 index 4f2d9e91..00000000 --- a/libraries/python/function-registry/pyproject.toml +++ /dev/null @@ -1,32 +0,0 @@ -[project] -name = "function-registry" -version = "0.1.0" -description = "MADE:Exploration Function Registry" -authors = [{name="MADE:Explorers"}] -readme = "README.md" -requires-python = ">=3.11" -dependencies = [ - "openai>=1.16.1", - "pydantic>=2.6.1", - "pydantic-settings>=2.3.4", - "python-dotenv>=1.0.1", - "requests>=2.32.0", - "tiktoken>=0.7.0", - "azure-identity>=1.17.1", - "context>=0.1.0", -] - -[tool.uv] -package = true -dev-dependencies = [ - "pytest>=8.3.1", - "pytest-asyncio>=0.23.8", - "pytest-repeat>=0.9.3", -] - -[tool.uv.sources] -context = { path = "../context", editable = true } - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" diff --git a/libraries/python/function-registry/uv.lock b/libraries/python/function-registry/uv.lock deleted file mode 100644 index 4e4e8c94..00000000 --- a/libraries/python/function-registry/uv.lock +++ /dev/null @@ -1,732 +0,0 @@ -version = 1 -requires-python = ">=3.11" -resolution-markers = [ - "python_full_version < '3.13'", - "python_full_version >= '3.13'", -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, -] - -[[package]] -name = "anyio" -version = "4.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 }, -] - -[[package]] -name = "azure-core" -version = "1.31.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, - { name = "six" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/03/7a/f79ad135a276a37e61168495697c14ba1721a52c3eab4dae2941929c79f8/azure_core-1.31.0.tar.gz", hash = "sha256:656a0dd61e1869b1506b7c6a3b31d62f15984b1a573d6326f6aa2f3e4123284b", size = 277147 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/8e/fcb6a77d3029d2a7356f38dbc77cf7daa113b81ddab76b5593d23321e44c/azure_core-1.31.0-py3-none-any.whl", hash = "sha256:22954de3777e0250029360ef31d80448ef1be13b80a459bff80ba7073379e2cd", size = 197399 }, -] - -[[package]] -name = "azure-identity" -version = "1.18.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "msal" }, - { name = "msal-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/5d/1c7da35dd640b4a95a38f980bb6b0b56c4e91d5b3d518ac11a2c4ebf5f62/azure_identity-1.18.0.tar.gz", hash = "sha256:f567579a65d8932fa913c76eddf3305101a15e5727a5e4aa5df649a0f553d4c3", size = 263322 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/71/1d1bb387b6acaa5daa3e703c70dde3d54823ccd229bd6730de6e724f296e/azure_identity-1.18.0-py3-none-any.whl", hash = "sha256:bccf6106245b49ff41d0c4cd7b72851c5a2ba3a32cef7589da246f5727f26f02", size = 187179 }, -] - -[[package]] -name = "certifi" -version = "2024.8.30" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, -] - -[[package]] -name = "cffi" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, - { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, - { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, - { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, - { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, - { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, - { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, - { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, - { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, - { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, - { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, - { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, - { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, - { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, - { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, - { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, - { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, - { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, - { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, - { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, - { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, - { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, - { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, - { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, - { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, - { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, - { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, - { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, - { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, - { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, - { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "context" -version = "0.1.0" -source = { editable = "../context" } -dependencies = [ - { name = "events" }, -] - -[package.metadata] -requires-dist = [{ name = "events", editable = "../events" }] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - -[[package]] -name = "cryptography" -version = "43.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222 }, - { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751 }, - { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827 }, - { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034 }, - { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407 }, - { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457 }, - { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499 }, - { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504 }, - { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456 }, - { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263 }, - { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368 }, - { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750 }, - { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925 }, - { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152 }, - { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392 }, - { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 }, - { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 }, - { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, -] - -[[package]] -name = "events" -version = "0.1.0" -source = { editable = "../events" } -dependencies = [ - { name = "pydantic" }, -] - -[package.metadata] -requires-dist = [{ name = "pydantic", specifier = ">=2.6.1" }] - -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-repeat" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - -[[package]] -name = "h11" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, -] - -[[package]] -name = "httpcore" -version = "1.0.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 }, -] - -[[package]] -name = "httpx" -version = "0.27.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, -] - -[[package]] -name = "jiter" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/1a/aa64be757afc614484b370a4d9fc1747dc9237b37ce464f7f9d9ca2a3d38/jiter-0.5.0.tar.gz", hash = "sha256:1d916ba875bcab5c5f7d927df998c4cb694d27dceddf3392e58beaf10563368a", size = 158300 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/5f/3ac960ed598726aae46edea916e6df4df7ff6fe084bc60774b95cf3154e6/jiter-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4c8e1ed0ef31ad29cae5ea16b9e41529eb50a7fba70600008e9f8de6376d553", size = 284131 }, - { url = "https://files.pythonhosted.org/packages/03/eb/2308fa5f5c14c97c4c7720fef9465f1fa0771826cddb4eec9866bdd88846/jiter-0.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6f16e21276074a12d8421692515b3fd6d2ea9c94fd0734c39a12960a20e85f3", size = 299310 }, - { url = "https://files.pythonhosted.org/packages/3c/f6/dba34ca10b44715fa5302b8e8d2113f72eb00a9297ddf3fa0ae4fd22d1d1/jiter-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5280e68e7740c8c128d3ae5ab63335ce6d1fb6603d3b809637b11713487af9e6", size = 332282 }, - { url = "https://files.pythonhosted.org/packages/69/f7/64e0a7439790ec47f7681adb3871c9d9c45fff771102490bbee5e92c00b7/jiter-0.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:583c57fc30cc1fec360e66323aadd7fc3edeec01289bfafc35d3b9dcb29495e4", size = 342370 }, - { url = "https://files.pythonhosted.org/packages/55/31/1efbfff2ae8e4d919144c53db19b828049ad0622a670be3bbea94a86282c/jiter-0.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26351cc14507bdf466b5f99aba3df3143a59da75799bf64a53a3ad3155ecded9", size = 363591 }, - { url = "https://files.pythonhosted.org/packages/30/c3/7ab2ca2276426a7398c6dfb651e38dbc81954c79a3bfbc36c514d8599499/jiter-0.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829df14d656b3fb87e50ae8b48253a8851c707da9f30d45aacab2aa2ba2d614", size = 378551 }, - { url = "https://files.pythonhosted.org/packages/47/e7/5d88031cd743c62199b125181a591b1671df3ff2f6e102df85c58d8f7d31/jiter-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42a4bdcf7307b86cb863b2fb9bb55029b422d8f86276a50487982d99eed7c6e", size = 319152 }, - { url = "https://files.pythonhosted.org/packages/4c/2d/09ea58e1adca9f0359f3d41ef44a1a18e59518d7c43a21f4ece9e72e28c0/jiter-0.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04d461ad0aebf696f8da13c99bc1b3e06f66ecf6cfd56254cc402f6385231c06", size = 357377 }, - { url = "https://files.pythonhosted.org/packages/7d/2f/83ff1058cb56fc3ff73e0d3c6440703ddc9cdb7f759b00cfbde8228fc435/jiter-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6375923c5f19888c9226582a124b77b622f8fd0018b843c45eeb19d9701c403", size = 511091 }, - { url = "https://files.pythonhosted.org/packages/ae/c9/4f85f97c9894382ab457382337aea0012711baaa17f2ed55c0ff25f3668a/jiter-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cec323a853c24fd0472517113768c92ae0be8f8c384ef4441d3632da8baa646", size = 492948 }, - { url = "https://files.pythonhosted.org/packages/4d/f2/2e987e0eb465e064c5f52c2f29c8d955452e3b316746e326269263bfb1b7/jiter-0.5.0-cp311-none-win32.whl", hash = "sha256:aa1db0967130b5cab63dfe4d6ff547c88b2a394c3410db64744d491df7f069bb", size = 195183 }, - { url = "https://files.pythonhosted.org/packages/ab/59/05d1c3203c349b37c4dd28b02b9b4e5915a7bcbd9319173b4548a67d2e93/jiter-0.5.0-cp311-none-win_amd64.whl", hash = "sha256:aa9d2b85b2ed7dc7697597dcfaac66e63c1b3028652f751c81c65a9f220899ae", size = 191032 }, - { url = "https://files.pythonhosted.org/packages/aa/bd/c3950e2c478161e131bed8cb67c36aed418190e2a961a1c981e69954e54b/jiter-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9f664e7351604f91dcdd557603c57fc0d551bc65cc0a732fdacbf73ad335049a", size = 283511 }, - { url = "https://files.pythonhosted.org/packages/80/1c/8ce58d8c37a589eeaaa5d07d131fd31043886f5e77ab50c00a66d869a361/jiter-0.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:044f2f1148b5248ad2c8c3afb43430dccf676c5a5834d2f5089a4e6c5bbd64df", size = 296974 }, - { url = "https://files.pythonhosted.org/packages/4d/b8/6faeff9eed8952bed93a77ea1cffae7b946795b88eafd1a60e87a67b09e0/jiter-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:702e3520384c88b6e270c55c772d4bd6d7b150608dcc94dea87ceba1b6391248", size = 331897 }, - { url = "https://files.pythonhosted.org/packages/4f/54/1d9a2209b46d39ce6f0cef3ad87c462f9c50312ab84585e6bd5541292b35/jiter-0.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:528d742dcde73fad9d63e8242c036ab4a84389a56e04efd854062b660f559544", size = 342962 }, - { url = "https://files.pythonhosted.org/packages/2a/de/90360be7fc54b2b4c2dfe79eb4ed1f659fce9c96682e6a0be4bbe71371f7/jiter-0.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf80e5fe6ab582c82f0c3331df27a7e1565e2dcf06265afd5173d809cdbf9ba", size = 363844 }, - { url = "https://files.pythonhosted.org/packages/ba/ad/ef32b173191b7a53ea8a6757b80723cba321f8469834825e8c71c96bde17/jiter-0.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44dfc9ddfb9b51a5626568ef4e55ada462b7328996294fe4d36de02fce42721f", size = 378709 }, - { url = "https://files.pythonhosted.org/packages/07/de/353ce53743c0defbbbd652e89c106a97dbbac4eb42c95920b74b5056b93a/jiter-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c451f7922992751a936b96c5f5b9bb9312243d9b754c34b33d0cb72c84669f4e", size = 319038 }, - { url = "https://files.pythonhosted.org/packages/3f/92/42d47310bf9530b9dece9e2d7c6d51cf419af5586ededaf5e66622d160e2/jiter-0.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:308fce789a2f093dca1ff91ac391f11a9f99c35369117ad5a5c6c4903e1b3e3a", size = 357763 }, - { url = "https://files.pythonhosted.org/packages/bd/8c/2bb76a9a84474d48fdd133d3445db8a4413da4e87c23879d917e000a9d87/jiter-0.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7f5ad4a7c6b0d90776fdefa294f662e8a86871e601309643de30bf94bb93a64e", size = 511031 }, - { url = "https://files.pythonhosted.org/packages/33/4f/9f23d79c0795e0a8e56e7988e8785c2dcda27e0ed37977256d50c77c6a19/jiter-0.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea189db75f8eca08807d02ae27929e890c7d47599ce3d0a6a5d41f2419ecf338", size = 493042 }, - { url = "https://files.pythonhosted.org/packages/df/67/8a4f975aa834b8aecdb6b131422390173928fd47f42f269dcc32034ab432/jiter-0.5.0-cp312-none-win32.whl", hash = "sha256:e3bbe3910c724b877846186c25fe3c802e105a2c1fc2b57d6688b9f8772026e4", size = 195405 }, - { url = "https://files.pythonhosted.org/packages/15/81/296b1e25c43db67848728cdab34ac3eb5c5cbb4955ceb3f51ae60d4a5e3d/jiter-0.5.0-cp312-none-win_amd64.whl", hash = "sha256:a586832f70c3f1481732919215f36d41c59ca080fa27a65cf23d9490e75b2ef5", size = 189720 }, -] - -[[package]] -name = "msal" -version = "1.31.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/04/8d7aa5c671a26ca5612257fd419f97380ba89cdd231b2eb67df58483796d/msal-1.31.0.tar.gz", hash = "sha256:2c4f189cf9cc8f00c80045f66d39b7c0f3ed45873fd3d1f2af9f22db2e12ff4b", size = 144950 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/40/0a5d743484e1ad00493bdffa8d10d7dbc6a51fec95290ad396e47e79fa43/msal-1.31.0-py3-none-any.whl", hash = "sha256:96bc37cff82ebe4b160d5fc0f1196f6ca8b50e274ecd0ec5bf69c438514086e7", size = 113109 }, -] - -[[package]] -name = "msal-extensions" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "msal" }, - { name = "portalocker" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/38/ad49272d0a5af95f7a0cb64a79bbd75c9c187f3b789385a143d8d537a5eb/msal_extensions-1.2.0.tar.gz", hash = "sha256:6f41b320bfd2933d631a215c91ca0dd3e67d84bd1a2f50ce917d5874ec646bef", size = 22391 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/69/314d887a01599669fb330da14e5c6ff5f138609e322812a942a74ef9b765/msal_extensions-1.2.0-py3-none-any.whl", hash = "sha256:cf5ba83a2113fa6dc011a254a72f1c223c88d7dfad74cc30617c4679a417704d", size = 19254 }, -] - -[[package]] -name = "openai" -version = "1.51.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/af/cc59b1447f5a02bb1f25b9b0cd94b607aa2c969a81d9a244d4067f91f6fe/openai-1.51.0.tar.gz", hash = "sha256:8dc4f9d75ccdd5466fc8c99a952186eddceb9fd6ba694044773f3736a847149d", size = 306880 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/08/9f22356d4fbd273f734db1e6663b7ca6987943080567f5580471022e57ca/openai-1.51.0-py3-none-any.whl", hash = "sha256:d9affafb7e51e5a27dce78589d4964ce4d6f6d560307265933a94b2e3f3c5d2c", size = 383533 }, -] - -[[package]] -name = "packaging" -version = "24.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] - -[[package]] -name = "portalocker" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/fb/a70a4214956182e0d7a9099ab17d50bfcba1056188e9b14f35b9e2b62a0d/portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf", size = 18423 }, -] - -[[package]] -name = "pycparser" -version = "2.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, -] - -[[package]] -name = "pydantic" -version = "2.9.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, -] - -[[package]] -name = "pydantic-core" -version = "2.23.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, - { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, - { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, - { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, - { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, - { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, - { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, - { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, - { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, - { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, - { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, - { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, - { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, - { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, - { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, - { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, - { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, - { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, - { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, - { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, - { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, - { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, - { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, - { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, - { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, - { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, - { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, - { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, - { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, - { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, - { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, - { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, - { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, - { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, - { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, - { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, -] - -[[package]] -name = "pydantic-settings" -version = "2.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/27/0bed9dd26b93328b60a1402febc780e7be72b42847fa8b5c94b7d0aeb6d1/pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0", size = 70938 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/8d/29e82e333f32d9e2051c10764b906c2a6cd140992910b5f49762790911ba/pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907", size = 26864 }, -] - -[[package]] -name = "pyjwt" -version = "2.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - -[[package]] -name = "pytest" -version = "8.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, -] - -[[package]] -name = "pytest-asyncio" -version = "0.24.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 }, -] - -[[package]] -name = "pytest-repeat" -version = "0.9.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/5e/99365eb229efff0b1bd475886150fc6db9937ab7e1bd21f6f65c1279e0eb/pytest_repeat-0.9.3.tar.gz", hash = "sha256:ffd3836dfcd67bb270bec648b330e20be37d2966448c4148c4092d1e8aba8185", size = 6272 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/a8/0a0aec0c2541b8baf4a0b95af2ba99abce217ee43534adf9cb7c908cf184/pytest_repeat-0.9.3-py3-none-any.whl", hash = "sha256:26ab2df18226af9d5ce441c858f273121e92ff55f5bb311d25755b8d7abdd8ed", size = 4196 }, -] - -[[package]] -name = "python-dotenv" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, -] - -[[package]] -name = "pywin32" -version = "306" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 }, - { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 }, - { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 }, - { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 }, - { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 }, - { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 }, -] - -[[package]] -name = "regex" -version = "2024.9.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/38/148df33b4dbca3bd069b963acab5e0fa1a9dbd6820f8c322d0dd6faeff96/regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd", size = 399403 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/a1/d526b7b6095a0019aa360948c143aacfeb029919c898701ce7763bbe4c15/regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df", size = 482483 }, - { url = "https://files.pythonhosted.org/packages/32/d9/bfdd153179867c275719e381e1e8e84a97bd186740456a0dcb3e7125c205/regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268", size = 287442 }, - { url = "https://files.pythonhosted.org/packages/33/c4/60f3370735135e3a8d673ddcdb2507a8560d0e759e1398d366e43d000253/regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad", size = 284561 }, - { url = "https://files.pythonhosted.org/packages/b1/51/91a5ebdff17f9ec4973cb0aa9d37635efec1c6868654bbc25d1543aca4ec/regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679", size = 791779 }, - { url = "https://files.pythonhosted.org/packages/07/4a/022c5e6f0891a90cd7eb3d664d6c58ce2aba48bff107b00013f3d6167069/regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4", size = 832605 }, - { url = "https://files.pythonhosted.org/packages/ac/1c/3793990c8c83ca04e018151ddda83b83ecc41d89964f0f17749f027fc44d/regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664", size = 818556 }, - { url = "https://files.pythonhosted.org/packages/e9/5c/8b385afbfacb853730682c57be56225f9fe275c5bf02ac1fc88edbff316d/regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50", size = 792808 }, - { url = "https://files.pythonhosted.org/packages/9b/8b/a4723a838b53c771e9240951adde6af58c829fb6a6a28f554e8131f53839/regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199", size = 781115 }, - { url = "https://files.pythonhosted.org/packages/83/5f/031a04b6017033d65b261259c09043c06f4ef2d4eac841d0649d76d69541/regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4", size = 778155 }, - { url = "https://files.pythonhosted.org/packages/fd/cd/4660756070b03ce4a66663a43f6c6e7ebc2266cc6b4c586c167917185eb4/regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd", size = 784614 }, - { url = "https://files.pythonhosted.org/packages/93/8d/65b9bea7df120a7be8337c415b6d256ba786cbc9107cebba3bf8ff09da99/regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f", size = 853744 }, - { url = "https://files.pythonhosted.org/packages/96/a7/fba1eae75eb53a704475baf11bd44b3e6ccb95b316955027eb7748f24ef8/regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96", size = 855890 }, - { url = "https://files.pythonhosted.org/packages/45/14/d864b2db80a1a3358534392373e8a281d95b28c29c87d8548aed58813910/regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1", size = 781887 }, - { url = "https://files.pythonhosted.org/packages/4d/a9/bfb29b3de3eb11dc9b412603437023b8e6c02fb4e11311863d9bf62c403a/regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9", size = 261644 }, - { url = "https://files.pythonhosted.org/packages/c7/ab/1ad2511cf6a208fde57fafe49829cab8ca018128ab0d0b48973d8218634a/regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf", size = 274033 }, - { url = "https://files.pythonhosted.org/packages/6e/92/407531450762bed778eedbde04407f68cbd75d13cee96c6f8d6903d9c6c1/regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7", size = 483590 }, - { url = "https://files.pythonhosted.org/packages/8e/a2/048acbc5ae1f615adc6cba36cc45734e679b5f1e4e58c3c77f0ed611d4e2/regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231", size = 288175 }, - { url = "https://files.pythonhosted.org/packages/8a/ea/909d8620329ab710dfaf7b4adee41242ab7c9b95ea8d838e9bfe76244259/regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d", size = 284749 }, - { url = "https://files.pythonhosted.org/packages/ca/fa/521eb683b916389b4975337873e66954e0f6d8f91bd5774164a57b503185/regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64", size = 795181 }, - { url = "https://files.pythonhosted.org/packages/28/db/63047feddc3280cc242f9c74f7aeddc6ee662b1835f00046f57d5630c827/regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42", size = 835842 }, - { url = "https://files.pythonhosted.org/packages/e3/94/86adc259ff8ec26edf35fcca7e334566c1805c7493b192cb09679f9c3dee/regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766", size = 823533 }, - { url = "https://files.pythonhosted.org/packages/29/52/84662b6636061277cb857f658518aa7db6672bc6d1a3f503ccd5aefc581e/regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a", size = 797037 }, - { url = "https://files.pythonhosted.org/packages/c3/2a/cd4675dd987e4a7505f0364a958bc41f3b84942de9efaad0ef9a2646681c/regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9", size = 784106 }, - { url = "https://files.pythonhosted.org/packages/6f/75/3ea7ec29de0bbf42f21f812f48781d41e627d57a634f3f23947c9a46e303/regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d", size = 782468 }, - { url = "https://files.pythonhosted.org/packages/d3/67/15519d69b52c252b270e679cb578e22e0c02b8dd4e361f2b04efcc7f2335/regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822", size = 790324 }, - { url = "https://files.pythonhosted.org/packages/9c/71/eff77d3fe7ba08ab0672920059ec30d63fa7e41aa0fb61c562726e9bd721/regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0", size = 860214 }, - { url = "https://files.pythonhosted.org/packages/81/11/e1bdf84a72372e56f1ea4b833dd583b822a23138a616ace7ab57a0e11556/regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a", size = 859420 }, - { url = "https://files.pythonhosted.org/packages/ea/75/9753e9dcebfa7c3645563ef5c8a58f3a47e799c872165f37c55737dadd3e/regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a", size = 787333 }, - { url = "https://files.pythonhosted.org/packages/bc/4e/ba1cbca93141f7416624b3ae63573e785d4bc1834c8be44a8f0747919eca/regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776", size = 262058 }, - { url = "https://files.pythonhosted.org/packages/6e/16/efc5f194778bf43e5888209e5cec4b258005d37c613b67ae137df3b89c53/regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009", size = 273526 }, - { url = "https://files.pythonhosted.org/packages/93/0a/d1c6b9af1ff1e36832fe38d74d5c5bab913f2bdcbbd6bc0e7f3ce8b2f577/regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784", size = 483376 }, - { url = "https://files.pythonhosted.org/packages/a4/42/5910a050c105d7f750a72dcb49c30220c3ae4e2654e54aaaa0e9bc0584cb/regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36", size = 288112 }, - { url = "https://files.pythonhosted.org/packages/8d/56/0c262aff0e9224fa7ffce47b5458d373f4d3e3ff84e99b5ff0cb15e0b5b2/regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92", size = 284608 }, - { url = "https://files.pythonhosted.org/packages/b9/54/9fe8f9aec5007bbbbce28ba3d2e3eaca425f95387b7d1e84f0d137d25237/regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86", size = 795337 }, - { url = "https://files.pythonhosted.org/packages/b2/e7/6b2f642c3cded271c4f16cc4daa7231be544d30fe2b168e0223724b49a61/regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85", size = 835848 }, - { url = "https://files.pythonhosted.org/packages/cd/9e/187363bdf5d8c0e4662117b92aa32bf52f8f09620ae93abc7537d96d3311/regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963", size = 823503 }, - { url = "https://files.pythonhosted.org/packages/f8/10/601303b8ee93589f879664b0cfd3127949ff32b17f9b6c490fb201106c4d/regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6", size = 797049 }, - { url = "https://files.pythonhosted.org/packages/ef/1c/ea200f61ce9f341763f2717ab4daebe4422d83e9fd4ac5e33435fd3a148d/regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802", size = 784144 }, - { url = "https://files.pythonhosted.org/packages/d8/5c/d2429be49ef3292def7688401d3deb11702c13dcaecdc71d2b407421275b/regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29", size = 782483 }, - { url = "https://files.pythonhosted.org/packages/12/d9/cbc30f2ff7164f3b26a7760f87c54bf8b2faed286f60efd80350a51c5b99/regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8", size = 790320 }, - { url = "https://files.pythonhosted.org/packages/19/1d/43ed03a236313639da5a45e61bc553c8d41e925bcf29b0f8ecff0c2c3f25/regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84", size = 860435 }, - { url = "https://files.pythonhosted.org/packages/34/4f/5d04da61c7c56e785058a46349f7285ae3ebc0726c6ea7c5c70600a52233/regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554", size = 859571 }, - { url = "https://files.pythonhosted.org/packages/12/7f/8398c8155a3c70703a8e91c29532558186558e1aea44144b382faa2a6f7a/regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8", size = 787398 }, - { url = "https://files.pythonhosted.org/packages/58/3a/f5903977647a9a7e46d5535e9e96c194304aeeca7501240509bde2f9e17f/regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8", size = 262035 }, - { url = "https://files.pythonhosted.org/packages/ff/80/51ba3a4b7482f6011095b3a036e07374f64de180b7d870b704ed22509002/regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f", size = 273510 }, -] - -[[package]] -name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, -] - -[[package]] -name = "six" -version = "1.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, -] - -[[package]] -name = "tiktoken" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "regex" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/4a/abaec53e93e3ef37224a4dd9e2fc6bb871e7a538c2b6b9d2a6397271daf4/tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6", size = 33437 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/eb/57492b2568eea1d546da5cc1ae7559d924275280db80ba07e6f9b89a914b/tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f", size = 961468 }, - { url = "https://files.pythonhosted.org/packages/30/ef/e07dbfcb2f85c84abaa1b035a9279575a8da0236305491dc22ae099327f7/tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f", size = 907005 }, - { url = "https://files.pythonhosted.org/packages/ea/9b/f36db825b1e9904c3a2646439cb9923fc1e09208e2e071c6d9dd64ead131/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b", size = 1049183 }, - { url = "https://files.pythonhosted.org/packages/61/b4/b80d1fe33015e782074e96bbbf4108ccd283b8deea86fb43c15d18b7c351/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992", size = 1080830 }, - { url = "https://files.pythonhosted.org/packages/2a/40/c66ff3a21af6d62a7e0ff428d12002c4e0389f776d3ff96dcaa0bb354eee/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1", size = 1092967 }, - { url = "https://files.pythonhosted.org/packages/2e/80/f4c9e255ff236e6a69ce44b927629cefc1b63d3a00e2d1c9ed540c9492d2/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89", size = 1142682 }, - { url = "https://files.pythonhosted.org/packages/b1/10/c04b4ff592a5f46b28ebf4c2353f735c02ae7f0ce1b165d00748ced6467e/tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb", size = 799009 }, - { url = "https://files.pythonhosted.org/packages/1d/46/4cdda4186ce900608f522da34acf442363346688c71b938a90a52d7b84cc/tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908", size = 960446 }, - { url = "https://files.pythonhosted.org/packages/b6/30/09ced367d280072d7a3e21f34263dfbbf6378661e7a0f6414e7c18971083/tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410", size = 906652 }, - { url = "https://files.pythonhosted.org/packages/e6/7b/c949e4954441a879a67626963dff69096e3c774758b9f2bb0853f7b4e1e7/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704", size = 1047904 }, - { url = "https://files.pythonhosted.org/packages/50/81/1842a22f15586072280364c2ab1e40835adaf64e42fe80e52aff921ee021/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350", size = 1079836 }, - { url = "https://files.pythonhosted.org/packages/6d/87/51a133a3d5307cf7ae3754249b0faaa91d3414b85c3d36f80b54d6817aa6/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4", size = 1092472 }, - { url = "https://files.pythonhosted.org/packages/a5/1f/c93517dc6d3b2c9e988b8e24f87a8b2d4a4ab28920a3a3f3ea338397ae0c/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97", size = 1141881 }, - { url = "https://files.pythonhosted.org/packages/bf/4b/48ca098cb580c099b5058bf62c4cb5e90ca6130fa43ef4df27088536245b/tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f", size = 799281 }, -] - -[[package]] -name = "tqdm" -version = "4.66.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351 }, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, -] diff --git a/libraries/python/chat-driver/README.md b/libraries/python/openai-client/openai_client/chat_driver/README.md similarity index 51% rename from libraries/python/chat-driver/README.md rename to libraries/python/openai-client/openai_client/chat_driver/README.md index f39272e9..3a554056 100644 --- a/libraries/python/chat-driver/README.md +++ b/libraries/python/openai-client/openai_client/chat_driver/README.md @@ -7,21 +7,15 @@ You can register functions to be used as either _commands_ (allowing the user to issue them with a `/` message) or _tool functions_ (allowing the assistant to optionally call them as it generates a response) or both. -Session state and chat history are maintained for you. +Chat history is maintained for you in-memory. We also provide a local message +provider that will store chat history in a file, or you can implement your own +message provider. -All interactions with the OpenAI service can be logged. +All interactions with the OpenAI are saved as "metadata" on the request allowing +you to do whatever you'd like with it. It is logged for you. For users of the Semantic Workbench, an additional module is provided to make it simple to create an AsyncClient from Workbench-type config. See [this notebook](../skills/notebooks/notebooks/chat_driver.ipynb) for usage examples. - -## Future Work - -- This chat driver does not yet support json_schema nicely. The - ChatCompletionAPI recently added a TypeChat-like capability to ensure the - responses are valid JSON of the type specified in the request. Adding that - here would make a lot of sense. -- This chat driver does not yet support OpenAI-like "File search" or Claude-like - "artifacts". Adding something like that here would make a lot of sense. diff --git a/libraries/python/openai-client/openai_client/chat_driver/__init__.py b/libraries/python/openai-client/openai_client/chat_driver/__init__.py new file mode 100644 index 00000000..71ab1870 --- /dev/null +++ b/libraries/python/openai-client/openai_client/chat_driver/__init__.py @@ -0,0 +1,19 @@ +from .chat_driver import ( + ChatDriver, + ChatDriverConfig, +) +from .message_history_providers import ( + InMemoryMessageHistoryProvider, + LocalMessageHistoryProvider, + LocalMessageHistoryProviderConfig, + MessageHistoryProviderProtocol, +) + +__all__ = [ + "ChatDriver", + "ChatDriverConfig", + "InMemoryMessageHistoryProvider", + "LocalMessageHistoryProvider", + "LocalMessageHistoryProviderConfig", + "MessageHistoryProviderProtocol", +] diff --git a/libraries/python/chat-driver/chat_driver/chat_driver.py b/libraries/python/openai-client/openai_client/chat_driver/chat_driver.py similarity index 73% rename from libraries/python/chat-driver/chat_driver/chat_driver.py rename to libraries/python/openai-client/openai_client/chat_driver/chat_driver.py index 9da1016a..22119ffc 100644 --- a/libraries/python/chat-driver/chat_driver/chat_driver.py +++ b/libraries/python/openai-client/openai_client/chat_driver/chat_driver.py @@ -1,9 +1,7 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Any, Callable, Union -from context import Context, ContextProtocol from events import BaseEvent, ErrorEvent, MessageEvent -from function_registry.function_registry import FunctionRegistry from openai import AsyncOpenAI from openai.types.chat import ( ChatCompletionMessageParam, @@ -11,17 +9,14 @@ ChatCompletionUserMessageParam, ) from openai.types.chat.completion_create_params import ResponseFormat +from pydantic import BaseModel + from openai_client.completion import TEXT_RESPONSE_FORMAT, message_content_from_completion from openai_client.errors import CompletionError from openai_client.messages import MessageFormatter, format_with_dict -from openai_client.tools import complete_with_tool_calls, function_list_to_tools, function_registry_to_tools -from pydantic import BaseModel +from openai_client.tools import ToolFunction, ToolFunctions, complete_with_tool_calls, function_list_to_tool_choice -from .local_message_history_provider import ( - LocalMessageHistoryProvider, - LocalMessageHistoryProviderConfig, -) -from .message_history_provider import MessageHistoryProviderProtocol +from .message_history_providers import InMemoryMessageHistoryProvider, MessageHistoryProviderProtocol @dataclass @@ -30,43 +25,37 @@ class ChatDriverConfig: model: str instructions: str | list[str] = "You are a helpful assistant." instruction_formatter: MessageFormatter | None = None - context: ContextProtocol | None = None message_provider: MessageHistoryProviderProtocol | None = None - commands: list[Callable] = field(default_factory=list) - functions: list[Callable] = field(default_factory=list) + commands: list[Callable] | None = None + functions: list[Callable] | None = None class ChatDriver: """ A ChatDriver is a class that manages a conversation with a user. It provides methods to add messages to the conversation, and to generate responses to - user messages. The ChatDriver uses the OpenAI API to generate responses, and - can call functions registered with the ChatDriver to generate parts of the - response. The ChatDriver also provides a way to register commands that can - be called by the user to execute functions directly. + user messages. The ChatDriver uses the OpenAI Chat Completion API to + generate responses, and can call functions registered with the ChatDriver to + generate parts of the response. The ChatDriver also provides a way to + register commands that can be called by the user to execute functions + directly. Instructions are messages that are sent to the OpenAI model before any other messages. These instructions are used to guide the model in generating a response. The ChatDriver allows you to set instructions that can be formatted with variables. - If you want to just generate responses using the OpenAI API, you should use - the client directly (but we do have some helpers in the openai_helpers - module) to make this simpler. + If you want to just generate responses using the OpenAI Chat Completion API, + you should use the client directly (but we do have some helpers in the + openai_helpers module) to make this simpler. """ def __init__(self, config: ChatDriverConfig) -> None: - # A context object holds information about the current session, such as - # the session ID, the user ID, and the conversation ID. It also provides - # a method to emit events. If you do not supply one, one will be created - # for you with a random session ID. - self.context: ContextProtocol = config.context or Context() - # Set up a default message provider. This provider stores messages in a # local file. You can replace this with your own message provider that # implements the MessageHistoryProviderProtocol. - self.message_provider: MessageHistoryProviderProtocol = config.message_provider or LocalMessageHistoryProvider( - LocalMessageHistoryProviderConfig(context=self.context) + self.message_provider: MessageHistoryProviderProtocol = ( + config.message_provider or InMemoryMessageHistoryProvider() ) self.instructions: list[str] = ( @@ -83,15 +72,17 @@ def __init__(self, config: ChatDriverConfig) -> None: # call to a function, the function will be executed, the result passed # back to the model, and the model will continue generating the # response. - self.function_registry = FunctionRegistry(self.context, config.functions) - self.functions = self.function_registry.functions + self.function_list = ToolFunctions(functions=[ToolFunction(function) for function in (config.functions or [])]) + self.functions = self.function_list.functions # Commands are functions that can be called by the user by typing a # command in the chat. When a command is received, the chat driver will # execute the corresponding function and return the result to the user # directly. - self.command_registry = FunctionRegistry(self.context, config.commands) - self.commands = self.command_registry.functions + self.command_list = ToolFunctions( + functions=[ToolFunction(function) for function in (config.commands or [])], with_help=True + ) + self.commands = self.command_list.functions def _formatted_instructions(self, vars: dict[str, Any] | None) -> list[ChatCompletionSystemMessageParam]: return ChatDriver.format_instructions(self.instructions, vars, self.instruction_formatter) @@ -101,18 +92,20 @@ async def add_message(self, message: ChatCompletionMessageParam) -> None: # Commands are available to be run by the user message. def register_command(self, function: Callable) -> None: - self.command_registry.register_function(function) + self.command_list.add_function(function) def register_commands(self, functions: list[Callable]) -> None: - self.command_registry.register_functions(functions) + for function in functions: + self.register_command(function) # Functions are available to be called by the model during response # generation. def register_function(self, function: Callable) -> None: - self.function_registry.register_function(function) + self.function_list.add_function(function) def register_functions(self, functions: list[Callable]) -> None: - self.function_registry.register_functions(functions) + for function in functions: + self.register_function # Sometimes we want to register a function to be used by both the user and # the model. @@ -125,10 +118,10 @@ def register_functions_and_commands(self, functions: list[Callable]) -> None: self.register_functions(functions) def get_functions(self) -> list[Callable]: - return [function.fn for function in self.function_registry.get_functions()] + return [function.fn for function in self.function_list.get_functions()] def get_commands(self) -> list[Callable]: - commands = [function.fn for function in self.command_registry.get_functions()] + commands = [function.fn for function in self.command_list.get_functions()] return commands async def respond( @@ -158,7 +151,7 @@ async def respond( if message and message.startswith("/"): command_string = message[1:] try: - results = await self.command_registry.execute_function_string_with_string_response(command_string) + results = await self.command_list.execute_function_string(command_string, string_response=True) return MessageEvent(message=results) except Exception as e: return ErrorEvent(message=f"Error! {e}", metadata={"error": str(e)}) @@ -177,15 +170,14 @@ async def respond( completion_args = { "model": self.model, "messages": [*self._formatted_instructions(instruction_parameters), *(await self.message_provider.get())], - "tools": function_registry_to_tools(self.function_registry), - "tool_choice": function_list_to_tools(function_choice), "response_format": response_format, + "tool_choice": function_list_to_tool_choice(function_choice), } try: completion, new_messages = await complete_with_tool_calls( self.client, - self.function_registry, completion_args, + self.function_list, metadata=metadata, ) except CompletionError as e: @@ -211,7 +203,7 @@ def format_instructions( """ Chat Driver instructions are system messages given to the OpenAI model before any other messages. These instructions are used to guide the model in - generating a response. We oftentimes need inject variables into the + generating a response. We oftentimes need to inject variables into the instructions, so we provide a formatter function to format the instructions with the variables. This method returns a list of system messages formatted with the variables. diff --git a/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/__init__.py b/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/__init__.py new file mode 100644 index 00000000..e67e4661 --- /dev/null +++ b/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/__init__.py @@ -0,0 +1,13 @@ +from .in_memory_message_history_provider import InMemoryMessageHistoryProvider +from .local_message_history_provider import ( + LocalMessageHistoryProvider, + LocalMessageHistoryProviderConfig, +) +from .message_history_provider import MessageHistoryProviderProtocol + +__all__ = [ + "LocalMessageHistoryProvider", + "LocalMessageHistoryProviderConfig", + "InMemoryMessageHistoryProvider", + "MessageHistoryProviderProtocol", +] diff --git a/libraries/python/chat-driver/chat_driver/in_memory_message_history_provider.py b/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/in_memory_message_history_provider.py similarity index 100% rename from libraries/python/chat-driver/chat_driver/in_memory_message_history_provider.py rename to libraries/python/openai-client/openai_client/chat_driver/message_history_providers/in_memory_message_history_provider.py diff --git a/libraries/python/chat-driver/chat_driver/local_message_history_provider.py b/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/local_message_history_provider.py similarity index 67% rename from libraries/python/chat-driver/chat_driver/local_message_history_provider.py rename to libraries/python/openai-client/openai_client/chat_driver/message_history_providers/local_message_history_provider.py index ffa82045..a37bafce 100644 --- a/libraries/python/chat-driver/chat_driver/local_message_history_provider.py +++ b/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/local_message_history_provider.py @@ -2,20 +2,12 @@ from dataclasses import dataclass, field from os import PathLike from pathlib import Path -from typing import Any, Iterable +from typing import Any -from context.context import ContextProtocol from openai.types.chat import ( ChatCompletionMessageParam, - ChatCompletionMessageToolCallParam, -) -from openai_client.messages import ( - MessageFormatter, - create_assistant_message, - create_system_message, - create_user_message, - format_with_dict, ) +from openai_client.messages import MessageFormatter, format_with_liquid from .message_history_provider import MessageHistoryProviderProtocol @@ -24,7 +16,7 @@ @dataclass class LocalMessageHistoryProviderConfig: - context: ContextProtocol + session_id: str data_dir: PathLike | str | None = None messages: list[ChatCompletionMessageParam] = field(default_factory=list) formatter: MessageFormatter | None = None @@ -33,10 +25,10 @@ class LocalMessageHistoryProviderConfig: class LocalMessageHistoryProvider(MessageHistoryProviderProtocol): def __init__(self, config: LocalMessageHistoryProviderConfig) -> None: if not config.data_dir: - self.data_dir = DEFAULT_DATA_DIR / "chat_driver" / config.context.session_id + self.data_dir = DEFAULT_DATA_DIR / "chat_driver" / config.session_id else: self.data_dir = Path(config.data_dir) - self.formatter: MessageFormatter = config.formatter or format_with_dict + self.formatter = config.formatter or format_with_liquid # Create the messages file if it doesn't exist. if not self.data_dir.exists(): @@ -77,18 +69,3 @@ async def set(self, messages: list[ChatCompletionMessageParam], vars: dict[str, def delete_all(self) -> None: self.messages_file.write_text("[]") - - async def append_system_message(self, content: str, var: dict[str, Any] | None = None) -> None: - await self.append(create_system_message(content, var, self.formatter)) - - async def append_user_message(self, content: str, var: dict[str, Any] | None = None) -> None: - await self.append(create_user_message(content, var, self.formatter)) - - async def append_assistant_message( - self, - content: str, - refusal: str, - tool_calls: Iterable[ChatCompletionMessageToolCallParam] | None = None, - var: dict[str, Any] | None = None, - ) -> None: - await self.append(create_assistant_message(content, refusal, tool_calls, var, self.formatter)) diff --git a/libraries/python/chat-driver/chat_driver/message_history_provider.py b/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/message_history_provider.py similarity index 100% rename from libraries/python/chat-driver/chat_driver/message_history_provider.py rename to libraries/python/openai-client/openai_client/chat_driver/message_history_providers/message_history_provider.py diff --git a/libraries/python/chat-driver/tests/formatted_instructions_test.py b/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/tests/formatted_instructions_test.py similarity index 95% rename from libraries/python/chat-driver/tests/formatted_instructions_test.py rename to libraries/python/openai-client/openai_client/chat_driver/message_history_providers/tests/formatted_instructions_test.py index c79ef24c..06818dff 100644 --- a/libraries/python/chat-driver/tests/formatted_instructions_test.py +++ b/libraries/python/openai-client/openai_client/chat_driver/message_history_providers/tests/formatted_instructions_test.py @@ -1,5 +1,5 @@ -from chat_driver.chat_driver import ChatDriver -from openai_client.messages import format_with_dict +from openai_client.chat_driver import ChatDriver +from openai_client.messages import format_with_liquid def test_formatted_instructions() -> None: @@ -34,7 +34,7 @@ def test_formatted_instructions() -> None: "user_feedback": user_feedback, "chat_history": chat_history, }, - formatter=format_with_dict, + formatter=format_with_liquid, ) expected = [ diff --git a/libraries/python/openai-client/openai_client/errors.py b/libraries/python/openai-client/openai_client/errors.py index a56260c8..fc2b4406 100644 --- a/libraries/python/openai-client/openai_client/errors.py +++ b/libraries/python/openai-client/openai_client/errors.py @@ -59,7 +59,7 @@ def __init__(self, error: Exception) -> None: super().__init__(message) -def validate_completion(completion: ParsedChatCompletion[None]) -> None: +def validate_completion(completion: ParsedChatCompletion | None) -> None: if completion is None: raise CompletionIsNoneError() diff --git a/libraries/python/openai-client/openai_client/messages.py b/libraries/python/openai-client/openai_client/messages.py index 13b981ae..15437f03 100644 --- a/libraries/python/openai-client/openai_client/messages.py +++ b/libraries/python/openai-client/openai_client/messages.py @@ -83,33 +83,33 @@ def apply_truncation_to_dict(dict_: dict, maximum_length: int, filler_text: str) MessageFormatter = Callable[[str, dict[str, Any]], str] -def format_with_dict(value: str, vars: dict[str, Any]) -> str: +def format_with_dict(template: str, vars: dict[str, Any]) -> str: """ Format a string with the given variables using the Python format method. """ - if value and vars: - for key, value in vars.items(): - try: - value = value.format(**{key: value}) - except KeyError: - pass - return value + parsed = template + for key, value in vars.items(): + try: + parsed = template.format(**{key: value}) + except KeyError: + pass + return parsed -def format_with_liquid(value: str, vars: dict[str, Any]) -> str: +def format_with_liquid(template: str, vars: dict[str, Any]) -> str: """ Format a string with the given variables using the Liquid template engine. """ - out = value - if not value: - return value - template = Template(value) - out = template.render(**vars) - return out + parsed = template + if not vars: + return template + liquid_template = Template(template) + parsed = liquid_template.render(**vars) + return parsed def create_system_message( - content: str, var: dict[str, Any] | None = None, formatter: MessageFormatter = format_with_dict + content: str, var: dict[str, Any] | None = None, formatter: MessageFormatter = format_with_liquid ) -> ChatCompletionSystemMessageParam: if var: content = formatter(content, var) @@ -117,7 +117,7 @@ def create_system_message( def create_user_message( - content: str, var: dict[str, Any] | None = None, formatter: MessageFormatter = format_with_dict + content: str, var: dict[str, Any] | None = None, formatter: MessageFormatter = format_with_liquid ) -> ChatCompletionUserMessageParam: if var: content = formatter(content, var) @@ -129,7 +129,7 @@ def create_assistant_message( refusal: Optional[str] = None, tool_calls: Iterable[ChatCompletionMessageToolCallParam] | None = None, var: dict[str, Any] | None = None, - formatter: MessageFormatter = format_with_dict, + formatter: MessageFormatter = format_with_liquid, ) -> ChatCompletionAssistantMessageParam: if var: content = formatter(content, var) diff --git a/libraries/python/openai-client/openai_client/tools.py b/libraries/python/openai-client/openai_client/tools.py index 64d6f59e..f17c4be0 100644 --- a/libraries/python/openai-client/openai_client/tools.py +++ b/libraries/python/openai-client/openai_client/tools.py @@ -1,8 +1,11 @@ +import ast +import inspect import json -from typing import Any, Iterable +from dataclasses import dataclass +from typing import Any, Callable, Iterable -from function_registry import FunctionRegistry from openai import ( + NOT_GIVEN, AsyncAzureOpenAI, AsyncOpenAI, NotGiven, @@ -13,6 +16,8 @@ ParsedChatCompletion, ParsedFunctionToolCall, ) +from pydantic import BaseModel, create_model +from pydantic.fields import FieldInfo from . import logger from .completion import assistant_message_from_completion @@ -20,57 +25,35 @@ from .logging import add_serializable_data, make_completion_args_serializable -async def execute_tool_call( - tool_call: ParsedFunctionToolCall, - function_registry: FunctionRegistry, -) -> ChatCompletionMessageParam | None: +def to_string(value: Any) -> str: """ - Execute a tool call function using a Function Registry and return the response as a message. + Convert a value to a string. This is a helper function to get the response + of a tool function call into a message. """ - function = tool_call.function - if function_registry.has_function(function.name): - logger.debug( - "Function call.", - extra=add_serializable_data({"name": function.name, "arguments": function.arguments}), - ) - try: - kwargs: dict[str, Any] = json.loads(function.arguments) - value = await function_registry.execute_function_with_string_response(function.name, (), kwargs) - except Exception as e: - logger.error("Error.", extra=add_serializable_data({"error": e})) - value = f"Error: {e}" - finally: - logger.debug( - "Function response.", extra=add_serializable_data({"tool_call_id": tool_call.id, "content": value}) - ) - return { - "role": "tool", - "content": value, - "tool_call_id": tool_call.id, - } + if value is None: + return "Function executed successfully." + elif isinstance(value, str): + return value + elif isinstance(value, (int, float)): + return str(value) + elif isinstance(value, dict): + return json.dumps(value) + elif isinstance(value, list): + return json.dumps(value, indent=2) + elif isinstance(value, tuple): + return json.dumps(value) + elif isinstance(value, BaseModel): + return value.model_dump_json(indent=2) else: - logger.error(f"Function not found: {function.name}") - + return str(value) -def function_registry_to_tools(function_registry: FunctionRegistry) -> Iterable[ChatCompletionToolParam] | NotGiven: - # Only the "functions" tool is available in the Chat API. - # https://platform.openai.com/docs/guides/function-calling - # If the only tool is the default function registry help function, then - # we don't want to tell the chat response that we have any tools. - if function_registry.list_functions() == ["help"]: - return NotGiven() - - return [ - ChatCompletionToolParam(**{ - "type": "function", - "function": func.schema, - }) - for func in function_registry.get_functions() - ] - - -def function_list_to_tools(functions: list[str] | None) -> Iterable[ChatCompletionToolParam] | None: +def function_list_to_tool_choice(functions: list[str] | None) -> Iterable[ChatCompletionToolParam] | None: + """ + Convert a list of function names to a list of ChatCompletionToolParam + objects. This is used in the Chat Completions API if you want to tell the + completion it MUST use a specific set of tool functions. + """ if not functions: return None return [ @@ -82,25 +65,378 @@ def function_list_to_tools(functions: list[str] | None) -> Iterable[ChatCompleti ] or None +@dataclass +class Parameter: + """ + Tool functions are described by their parameters. This dataclass + describes a single parameter of a tool function. + """ + + name: str + type: Any + description: str | None + default_value: Any | None = None + + +class ToolFunction: + """ + A tool function is a Python function that can be called as a tool from the + chat completion API. This class wraps a function so you can generate it's + JSON schema for the chat completion API, execute it with arguments, and + generate a usage string (for help messages) + """ + + def __init__(self, fn: Callable, name: str | None = None, description: str | None = None) -> None: + self.fn = fn + self.name = name or fn.__name__ + self.description = description or inspect.getdoc(fn) or self.name.replace("_", " ").title() + + def parameters(self, exclude: list[str] = []) -> list[Parameter]: + """ + This function's parameters and their default values. + """ + parameters = dict(inspect.signature(self.fn).parameters) + for param_name in exclude: + del parameters[param_name] + return [ + Parameter( + name=param_name, + type=param.annotation, + description=None, # param.annotation.description, + default_value=param.default, + ) + for param_name, param in parameters.items() + ] + + def usage(self) -> str: + """ + A usage string for this function. This can be used in help messages. + """ + name = self.name + param_usages = [] + for param in self.parameters(): + param_type = param.type + try: + param_type = param.type.__name__ + except AttributeError: + param_type = param.type + usage = f"{param.name}: {param_type}" + if param.default_value is not inspect.Parameter.empty: + if isinstance(param.default_value, str): + usage += f' = "{param.default_value}"' + else: + usage += f" = {param.default_value}" + param_usages.append(usage) + + description = self.description + return f"{name}({', '.join(param_usages)}): {description}" + + def schema(self, strict: bool = True) -> dict[str, Any]: + """ + Generate a JSON schema for this function that is suitable for the OpenAI + completion API. + """ + + # Create the Pydantic model using create_model. + model_name = self.fn.__name__.title().replace("_", "") + fields = {} + for parameter in self.parameters(): + field_info = FieldInfo(description=parameter.description) + if parameter.default_value is not inspect.Parameter.empty: + field_info.default = parameter.default_value + fields[parameter.name] = ( + parameter.type, + field_info, + ) + pydantic_model = create_model(model_name, **fields) + + # Generate the JSON schema from the Pydantic model. + parameters_schema = pydantic_model.model_json_schema(mode="serialization") + + # Remove title attribute from all properties (not allowed by the Chat + # Completions API). + properties = parameters_schema["properties"] + for property_key in properties.keys(): + if "title" in properties[property_key]: + del properties[property_key]["title"] + + # And from the top-level object. + if "title" in parameters_schema: + del parameters_schema["title"] + + # Output a schema that matches OpenAI's "tool" format. + # e.g., https://platform.openai.com/docs/guides/function-calling + # We use this because they trained GPT on it. + schema = { + # "$schema": "http://json-schema.org/draft-07/schema#", + # "$id": f"urn:jsonschema:{name}", + "name": self.name, + "description": self.description, + "strict": strict, + "parameters": { + "type": "object", + "properties": parameters_schema["properties"], + }, + } + + # If this is a strict schema, OpenAI requires additionalProperties to be + # False. "strict mode" is required for JSON or structured output from + # the API. + if strict: + schema["parameters"]["additionalProperties"] = False + + # Add required fields (another Chat Completions API requirement). + if "required" in parameters_schema: + schema["parameters"]["required"] = parameters_schema["required"] + + # Add type definitions (another Chat Completions API requirement). + if "$defs" in parameters_schema: + schema["parameters"]["$defs"] = parameters_schema["$defs"] + for key in schema["parameters"]["$defs"]: + schema["parameters"]["$defs"][key]["additionalProperties"] = False + + return schema + + async def execute(self, string_response: bool = False, *args, **kwargs) -> Any: + """ + Run this function, and return its value. If the function is a coroutine, + it will be awaited. If string_response is True, the response will be + converted to a string. + """ + try: + result = self.fn(*args, **kwargs) + if inspect.iscoroutine(result): + result = await result + if string_response: + return to_string(result) + return result + except Exception as e: + if string_response: + return f"Error running function {self.name}: {e}" + raise e + + +class FunctionHandler: + def __init__(self, tool_functions: "ToolFunctions") -> None: + self.tool_functions = tool_functions + + def __getattr__(self, name: str) -> Callable: + """Makes registered functions accessible as attributes of the functions object.""" + if name not in self.tool_functions.function_map: + raise AttributeError(f"'FunctionHandler' object has no attribute '{name}'") + + async def wrapper(*args, **kwargs) -> Any: + return await self.tool_functions.execute_function(name, args, kwargs) + + return wrapper + + +class ToolFunctions: + """ + A set of tool functions that can be called from the Chat Completions API. + Pass this into the `complete_with_tool_calls` helper function to run a full + tool-call completion against the API. + """ + + def __init__(self, functions: list[ToolFunction] | None = None, with_help: bool = False) -> None: + # Set up function map. + self.function_map = {} + if functions: + for function in functions: + self.function_map[function.name] = function + + # A help message can be generated for the function map. + if with_help: + self.function_map["help"] = ToolFunction(self.help) + + # This allows actions to be called as attributes. + self.functions = FunctionHandler(self) + + def help(self) -> str: + """Return this help message.""" + + usage = [f"{command.usage()}" for command in self.function_map.values()] + usage.sort() + return "Commands:\n" + "\n".join(usage) + + def add_function(self, function: Callable, name: str | None = None, description: str | None = None) -> None: + """Register a function with the tool functions.""" + if not name: + name = function.__name__ + self.function_map[name] = ToolFunction(function, name, description) + + def has_function(self, name: str) -> bool: + return name in self.function_map + + def get_function(self, name: str) -> ToolFunction | None: + return self.function_map.get(name) + + def get_functions(self) -> list[ToolFunction]: + return [function for function in self.function_map.values()] + + async def execute_function( + self, name: str, args: tuple = (), kwargs: dict[str, Any] = {}, string_response: bool = False + ) -> Any: + """ + Run a function from the ToolFunctions list by name. If string_response + is True, the function return value will be converted to a string. + """ + function = self.get_function(name) + if not function: + raise ValueError(f"Function {name} not found in registry.") + return await function.execute(string_response, *args, **kwargs) + + async def execute_function_string(self, function_string: str, string_response: bool = False) -> Any: + """Parse a function string and execute the function.""" + try: + function, args, kwargs = self.parse_function_string(function_string) + except ValueError as e: + raise ValueError(f"{e}. Type: `/help` for more information.") + if not function: + raise ValueError("Function not found in registry. Type: `/help` for more information.") + return await function.execute(string_response, *args, **kwargs) + + def parse_function_string(self, function_string: str) -> tuple[ToolFunction | None, list[Any], dict[str, Any]]: + """Parse a function call string into a function and its arguments.""" + + # As a convenience, remove any leading slashes. + function_string = function_string.lstrip("/") + + # As a convenience, add parentheses if they are missing. + if " " not in function_string and "(" not in function_string: + function_string += "()" + + # Parse the string into an AST (Abstract Syntax Tree) + try: + tree = ast.parse(function_string) + except SyntaxError: + raise ValueError("Invalid function call. Please check your syntax.") + + # Ensure the tree contains exactly one expression (the function call) + if not (isinstance(tree, ast.Module) and len(tree.body) == 1 and isinstance(tree.body[0], ast.Expr)): + raise ValueError("Expected a single function call.") + + # The function call is stored as a `Call` node within the expression + call_node = tree.body[0].value + if not isinstance(call_node, ast.Call): + raise ValueError("Invalid function call. Please check your syntax.") + + # Extract the function name + if isinstance(call_node.func, ast.Name): + function_name = call_node.func.id + else: + raise ValueError("Unsupported function format. Please check your syntax.") + + # Helper function to evaluate AST nodes to their Python equivalent + def eval_node(node): + if isinstance(node, ast.Constant): + return node.value + elif isinstance(node, ast.List): + return [eval_node(elem) for elem in node.elts] + elif isinstance(node, ast.Tuple): + return tuple(eval_node(elem) for elem in node.elts) + elif isinstance(node, ast.Dict): + return {eval_node(key): eval_node(value) for key, value in zip(node.keys, node.values)} + elif isinstance(node, ast.Name): + return node.id # This can return variable names, but we assume they're constants + elif isinstance(node, ast.BinOp): # Handling arithmetic expressions + return eval(compile(ast.Expression(node), filename="", mode="eval")) + elif isinstance(node, ast.Call): + raise ValueError("Nested function calls are not supported.") + else: + raise ValueError(f"Unsupported AST node type: {type(node).__name__}") + + # Extract positional arguments + args = [eval_node(arg) for arg in call_node.args] + + # Extract keyword arguments + kwargs = {} + for kw in call_node.keywords: + kwargs[kw.arg] = eval_node(kw.value) + + function = self.get_function(function_name) + if not function: + return None, [], {} + + return function, args, kwargs + + def chat_completion_tools(self) -> list[ChatCompletionToolParam] | NotGiven: + """ + Return a list of ChatCompletionToolParam objects that describe the tool + functions in this ToolFunctions object. These can be passed to the Chat + Completions API (in the "tools" parameter) to enable tool function + calls. + """ + tools = [ + ChatCompletionToolParam(**{ + "type": "function", + "function": func.schema(), + }) + for func in self.function_map.values() + ] + return tools or NOT_GIVEN + + async def execute_tool_call(self, tool_call: ParsedFunctionToolCall) -> ChatCompletionMessageParam | None: + """ + Execute a function as requested by a ParsedFunctionToolCall (generated + by the Chat Completions API) and return the response as a + ChatCompletionMessageParam message (as required by the Chat Completions + API) + """ + function = tool_call.function + if self.has_function(function.name): + logger.debug( + "Function call.", + extra=add_serializable_data({"name": function.name, "arguments": function.arguments}), + ) + try: + kwargs: dict[str, Any] = json.loads(function.arguments) + value = await self.execute_function(function.name, (), kwargs, string_response=True) + except Exception as e: + logger.error("Error.", extra=add_serializable_data({"error": e})) + value = f"Error: {e}" + finally: + logger.debug( + "Function response.", extra=add_serializable_data({"tool_call_id": tool_call.id, "content": value}) + ) + return { + "role": "tool", + "content": value, + "tool_call_id": tool_call.id, + } + else: + logger.error(f"Function not found: {function.name}") + return None + + async def complete_with_tool_calls( async_client: AsyncOpenAI | AsyncAzureOpenAI, - function_registry: FunctionRegistry, completion_args: dict[str, Any], + tool_functions: ToolFunctions, metadata: dict[str, Any] = {}, ) -> tuple[ParsedChatCompletion | None, list[ChatCompletionMessageParam]]: """ - Complete a chat response with tool calls handled by Function Registry-registered functions. + Complete a chat response with tool calls handled by the supplied tool + functions. Parameters: + - async_client: The OpenAI client. - - function_registry: The function registry. - - completion_args: The completion arguments passed onto the OpenAI `parse` call. See the OpenAI API docs for more information. + - completion_args: The completion arguments passed onto the OpenAI `parse` + call. See the OpenAI API docs for more information. + - tool_functions: A ToolFunctions object that contains the tool functions to + be available to be called. - metadata: Metadata to be added to the completion response. """ # Pull out a reference to the completion args messages. messages: list[ChatCompletionMessageParam] = completion_args.get("messages", []) new_messages: list[ChatCompletionMessageParam] = [] + # Set up the tools if tool_functions exists. + if tool_functions: + # Note: this overwrites any existing tools. + completion_args["tools"] = tool_functions.chat_completion_tools() + # Completion call. logger.debug("Completion call.", extra=add_serializable_data(make_completion_args_serializable(completion_args))) metadata["completion_args"] = make_completion_args_serializable(completion_args) @@ -131,23 +467,26 @@ async def complete_with_tool_calls( # Call all tool functions and generate return messages. for tool_call in completion_message.tool_calls: - function_call_result_message = await execute_tool_call(tool_call, function_registry) + function_call_result_message = await tool_functions.execute_tool_call(tool_call) if function_call_result_message: new_messages.append(function_call_result_message) # Completion call for final response. final_args = {**completion_args, "messages": [*messages, *new_messages]} - del final_args["tools"] - del final_args["tool_choice"] + if "tools" in final_args: + # TODO: We *could* allow a while "tools" loop and let the agent keep going? + del final_args["tools"] + if "tool_choice" in final_args: + del final_args["tool_choice"] logger.debug("Tool completion call.", extra=add_serializable_data(make_completion_args_serializable(final_args))) metadata["tool_completion_args"] = make_completion_args_serializable(final_args) try: - tool_completion = await async_client.beta.chat.completions.parse( + tool_completion: ParsedChatCompletion = await async_client.beta.chat.completions.parse( **final_args, ) validate_completion(tool_completion) logger.debug("Tool completion response.", extra=add_serializable_data({"completion": completion.model_dump()})) - metadata["completion"] = completion.model_dump() + metadata["tool_completion"] = completion.model_dump() except Exception as e: tool_completion_error = CompletionError(e) metadata["tool_completion_error"] = tool_completion_error.message diff --git a/libraries/python/openai-client/pyproject.toml b/libraries/python/openai-client/pyproject.toml index 9ad6c499..eb7e136c 100644 --- a/libraries/python/openai-client/pyproject.toml +++ b/libraries/python/openai-client/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "azure-ai-contentsafety>=1.0.0", "azure-core[aio]>=1.30.0", "azure-identity>=1.17.1", - "function-registry>=0.1.0", + "events>=0.1.0", "openai>=1.3.9", "pillow>=11.0.0", "python-liquid>=1.12.1", @@ -23,7 +23,7 @@ dev-dependencies = ["pytest>=8.3.3"] [tool.uv.sources] semantic-workbench-assistant = { path = "../semantic-workbench-assistant", editable = true } -function-registry = { path = "../function-registry", editable = true } +events = { path = "../events", editable = true } [build-system] requires = ["hatchling"] diff --git a/libraries/python/function-registry/function_registry/tests/test_command_parsing.py b/libraries/python/openai-client/tests/test_command_parsing.py similarity index 86% rename from libraries/python/function-registry/function_registry/tests/test_command_parsing.py rename to libraries/python/openai-client/tests/test_command_parsing.py index ded4b454..e1d3ba22 100644 --- a/libraries/python/function-registry/function_registry/tests/test_command_parsing.py +++ b/libraries/python/openai-client/tests/test_command_parsing.py @@ -1,15 +1,14 @@ from typing import Any, Callable import pytest -from context.context import Context -from function_registry.function_registry import FunctionRegistry +from openai_client.tools import ToolFunctions -def no_op(context: Context) -> None: +def no_op() -> None: pass -def echo(context: Context, value: Any) -> str: +def echo(value: Any) -> str: match value: case str(): return value @@ -23,9 +22,11 @@ def echo(context: Context, value: Any) -> str: return str(value) -context = Context() +# Create tool functions. functions = [echo, no_op] -register = FunctionRegistry(context, functions) +tf = ToolFunctions() +for func in functions: + tf.add_function(func) @pytest.mark.parametrize( @@ -63,7 +64,7 @@ def test_command_parsing_pythonic( expected_error: Any, ): try: - command, args, kwargs = register.parse_function_string(command_string) + command, args, kwargs = tf.parse_function_string(command_string) except Exception as e: assert expected_error is not None assert isinstance(e, expected_error) diff --git a/libraries/python/openai-client/tests/test_formatted_messages.py b/libraries/python/openai-client/tests/test_formatted_messages.py new file mode 100644 index 00000000..aa83ceca --- /dev/null +++ b/libraries/python/openai-client/tests/test_formatted_messages.py @@ -0,0 +1,48 @@ +from textwrap import dedent + +from openai_client.messages import format_with_liquid + + +def test_formatted_messages() -> None: + # Set instructions. + instructions = [ + dedent(""" + Generate an outline for the document, including title. The outline should include the key points that will be covered in the document. Consider the attachments, the rationale for why they were uploaded, and the conversation that has taken place. The outline should be a hierarchical structure with multiple levels of detail, and it should be clear and easy to understand. The outline should be generated in a way that is consistent with the document that will be generated from it. + """).strip(), + "{{chat_history}}", + "{% for attachment in attachments %}{{attachment.filename}}{{attachment.content}}{% endfor %}", + "{{outline_versions.last}}", + "{{user_feedback}}", + ] + + # Set vars. + attachments = [ + {"filename": "filename1", "content": "content1"}, + {"filename": "filename2", "content": "content2"}, + ] + outline_versions = ["outline1", "outline2"] + user_feedback = "feedback" + chat_history = "history" + + actual = [ + format_with_liquid( + template=instruction, + vars={ + "attachments": attachments, + "outline_versions": outline_versions, + "user_feedback": user_feedback, + "chat_history": chat_history, + }, + ) + for instruction in instructions + ] + + expected = [ + "Generate an outline for the document, including title. The outline should include the key points that will be covered in the document. Consider the attachments, the rationale for why they were uploaded, and the conversation that has taken place. The outline should be a hierarchical structure with multiple levels of detail, and it should be clear and easy to understand. The outline should be generated in a way that is consistent with the document that will be generated from it.", + "history", + "filename1content1filename2content2", + "outline2", + "feedback", + ] + + assert actual == expected diff --git a/libraries/python/openai-client/uv.lock b/libraries/python/openai-client/uv.lock index 17124e1a..6418aff3 100644 --- a/libraries/python/openai-client/uv.lock +++ b/libraries/python/openai-client/uv.lock @@ -301,24 +301,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] -[[package]] -name = "context" -version = "0.1.0" -source = { editable = "../context" } -dependencies = [ - { name = "events" }, -] - -[package.metadata] -requires-dist = [{ name = "events", editable = "../events" }] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "cryptography" version = "43.0.1" @@ -480,40 +462,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -814,7 +762,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -832,7 +780,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../function-registry" }, + { name = "events", editable = "../events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, diff --git a/libraries/python/skills/notebooks/notebooks/chat_driver.ipynb b/libraries/python/skills/notebooks/notebooks/chat_driver.ipynb index 9ee658ee..569df7f0 100644 --- a/libraries/python/skills/notebooks/notebooks/chat_driver.ipynb +++ b/libraries/python/skills/notebooks/notebooks/chat_driver.ipynb @@ -37,7 +37,21 @@ "import json\n", "from pathlib import Path\n", "\n", - "# Set up structured logging to a file.\n", + "LOGGING = {\n", + " \"version\": 1,\n", + " \"disable_existing_loggers\": False,\n", + " \"formatters\": {\n", + " \"json\": {\n", + " \"()\": \"pythonjsonlogger.jsonlogger.JsonFormatter\",\n", + " \"fmt\": \"%(asctime)s %(levelname)s %(name)s %(message)s\",\n", + "\n", + " }\n", + " },\n", + "}\n", + "\n", + "\n", + "# Set up structured logging to a file. All of the cells in this notebook use\n", + "# this logger. Find them at .data/logs.jsonl.\n", "class JsonFormatter(logging.Formatter):\n", " def format(self, record) -> str:\n", " record_dict = record.__dict__\n", @@ -84,8 +98,7 @@ " \"api_version\": os.environ.get(\"AZURE_OPENAI_API_VERSION\", \"\"),\n", " \"max_retries\": 2,\n", "}\n", - "\n", - "model = azure_openai_config.get(\"azure_deployment\", \"gpt-4o\")\n", + "logger.info(\"Azure OpenAI configuration\", extra=azure_openai_config)\n", "\n", "async_client = AsyncAzureOpenAI(\n", " **azure_openai_config,\n", @@ -101,7 +114,9 @@ " AzureCliCredential(),\n", " \"https://cognitiveservices.azure.com/.default\",\n", " ),\n", - ")" + ")\n", + "\n", + "model: str = azure_openai_config.get(\"azure_deployment\", \"gpt-4o\")\n" ] }, { @@ -126,9 +141,45 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"id\": \"chatcmpl-ASsXCOPTQYPT3ZLEzdoXzYpfPDLIi\",\n", + " \"choices\": [\n", + " {\n", + " \"finish_reason\": \"stop\",\n", + " \"index\": 0,\n", + " \"logprobs\": null,\n", + " \"message\": {\n", + " \"content\": \"This is a test.\",\n", + " \"refusal\": null,\n", + " \"role\": \"assistant\",\n", + " \"function_call\": null,\n", + " \"tool_calls\": null\n", + " }\n", + " }\n", + " ],\n", + " \"created\": 1731446178,\n", + " \"model\": \"gpt-4o-2024-08-06\",\n", + " \"object\": \"chat.completion\",\n", + " \"service_tier\": null,\n", + " \"system_fingerprint\": \"fp_d54531d9eb\",\n", + " \"usage\": {\n", + " \"completion_tokens\": 5,\n", + " \"prompt_tokens\": 12,\n", + " \"total_tokens\": 17,\n", + " \"completion_tokens_details\": null,\n", + " \"prompt_tokens_details\": null\n", + " }\n", + "}\n" + ] + } + ], "source": [ "completion = client.chat.completions.create(\n", " messages=[\n", @@ -139,7 +190,7 @@ " ],\n", " model=model,\n", ")\n", - "print(completion.model_dump())" + "print(completion.model_dump_json(indent=2))" ] }, { @@ -151,11 +202,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ChatCompletion(id='chatcmpl-ASsXGnCRUzKrj4CWU1Zbc1ZXaTRqm', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='This is a test.', refusal=None, role='assistant', function_call=None, tool_calls=None))], created=1731446182, model='gpt-4o-2024-08-06', object='chat.completion', service_tier=None, system_fingerprint='fp_d54531d9eb', usage=CompletionUsage(completion_tokens=5, prompt_tokens=12, total_tokens=17, completion_tokens_details=None, prompt_tokens_details=None))\n" + ] + } + ], "source": [ - "response = await async_client.chat.completions.create(\n", + "message_event = await async_client.chat.completions.create(\n", " messages=[\n", " {\n", " \"role\": \"user\",\n", @@ -164,7 +223,7 @@ " ],\n", " model=model,\n", ")\n", - "print(response)" + "print(message_event)" ] }, { @@ -176,9 +235,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'id': 'chatcmpl-AQgaEhy4ed19h72VpBu8nJBJE3g3L', 'choices': [{'delta': {'content': '', 'function_call': None, 'refusal': None, 'role': 'assistant', 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1730923582, 'model': 'gpt-4o-2024-08-06', 'object': 'chat.completion.chunk', 'service_tier': None, 'system_fingerprint': 'fp_d54531d9eb', 'usage': None}\n", + "{'id': 'chatcmpl-AQgaEhy4ed19h72VpBu8nJBJE3g3L', 'choices': [{'delta': {'content': 'This', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1730923582, 'model': 'gpt-4o-2024-08-06', 'object': 'chat.completion.chunk', 'service_tier': None, 'system_fingerprint': 'fp_d54531d9eb', 'usage': None}\n", + "{'id': 'chatcmpl-AQgaEhy4ed19h72VpBu8nJBJE3g3L', 'choices': [{'delta': {'content': ' is', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1730923582, 'model': 'gpt-4o-2024-08-06', 'object': 'chat.completion.chunk', 'service_tier': None, 'system_fingerprint': 'fp_d54531d9eb', 'usage': None}\n", + "{'id': 'chatcmpl-AQgaEhy4ed19h72VpBu8nJBJE3g3L', 'choices': [{'delta': {'content': ' a', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1730923582, 'model': 'gpt-4o-2024-08-06', 'object': 'chat.completion.chunk', 'service_tier': None, 'system_fingerprint': 'fp_d54531d9eb', 'usage': None}\n", + "{'id': 'chatcmpl-AQgaEhy4ed19h72VpBu8nJBJE3g3L', 'choices': [{'delta': {'content': ' test', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1730923582, 'model': 'gpt-4o-2024-08-06', 'object': 'chat.completion.chunk', 'service_tier': None, 'system_fingerprint': 'fp_d54531d9eb', 'usage': None}\n", + "{'id': 'chatcmpl-AQgaEhy4ed19h72VpBu8nJBJE3g3L', 'choices': [{'delta': {'content': '.', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1730923582, 'model': 'gpt-4o-2024-08-06', 'object': 'chat.completion.chunk', 'service_tier': None, 'system_fingerprint': 'fp_d54531d9eb', 'usage': None}\n", + "{'id': 'chatcmpl-AQgaEhy4ed19h72VpBu8nJBJE3g3L', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': 'stop', 'index': 0, 'logprobs': None}], 'created': 1730923582, 'model': 'gpt-4o-2024-08-06', 'object': 'chat.completion.chunk', 'service_tier': None, 'system_fingerprint': 'fp_d54531d9eb', 'usage': None}\n" + ] + } + ], "source": [ "stream = await async_client.chat.completions.create(\n", " messages=[\n", @@ -194,6 +267,510 @@ " print(chunk.model_dump())" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## OpenAI Helpers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Standardized response handling" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The future of AI is vast and holds immense potential to transform nearly every aspect of our lives. As we look forward, here are several key domains where AI is likely to make significant impacts:\n", + "\n", + "1. **Healthcare**: AI will continue to revolutionize healthcare through personalized medicine, improved diagnostic capabilities, and efficient drug discovery. Machine learning algorithms can analyze patient data to suggest tailored treatment plans, detect diseases earlier, and even manage administrative tasks.\n", + "\n", + "2. **Transportation**: Autonomous vehicles and AI-driven logistics will reshape transportation infrastructure and mobility. This includes not only self-driving cars but also the optimization of public transport systems and delivery services.\n", + "\n", + "3. **Environment**: AI will play a crucial role in addressing environmental challenges. From optimizing energy consumption to predicting and mitigating natural disasters, AI can help manage resources more sustainably and minimize human impact on the planet.\n", + "\n", + "4. **Workplace Automation**: While AI will automate routine tasks, it will also create opportunities for innovation and new job categories. We need to focus on reskilling and upskilling the workforce to harness AI as a tool for enhancing productivity and creativity.\n", + "\n", + "5. **Ethics and Governance**: As AI systems become more integrated into our lives, ethical considerations and robust governance frameworks will become increasingly important. We must ensure that AI is developed and deployed responsibly, with fairness, transparency, and accountability at the forefront.\n", + "\n", + "6. **Education**: AI-powered tools can provide personalized learning experiences, helping educators better meet the diverse needs of students and enabling lifelong learning.\n", + "\n", + "Ultimately, the future of AI depends on how we choose to guide its development. If approached thoughtfully and inclusively, it has the potential to enhance human capabilities, address global challenges, and improve quality of life across the globe. It's crucial that as we advance these technologies, we remain vigilant about their societal implications, striving for a future that benefits all.\n" + ] + } + ], + "source": [ + "from context import Context\n", + "from openai_client.errors import CompletionError, validate_completion\n", + "from openai_client.logging import make_completion_args_serializable, add_serializable_data\n", + "from openai_client.completion import message_content_from_completion\n", + "\n", + "context = Context(\"conversation-id-1005\")\n", + "\n", + "# We use a metadata dictionary in our helpers to store information about the\n", + "# completion request.\n", + "metadata = {}\n", + "\n", + "# This is just standard OpenAI completion request arguments.\n", + "completion_args = {\n", + " \"model\": model,\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"system\",\n", + " \"content\": \"You are a famous computer scientist. You are giving a talk at a conference. You are talking about the future of AI and how it will change the world. You are asked a questions by audience members and answer thoughtfully.\",\n", + " },\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"What is the future of AI?\",\n", + " }\n", + " ],\n", + "}\n", + "\n", + "# If we these completion args to logs and metadata, though, they need to be\n", + "# serializable. We have a helper for that.\n", + "metadata[\"completion_args\"] = make_completion_args_serializable(completion_args)\n", + "\n", + "# We have helpers for validating the response and handling exceptions in a\n", + "# standardized way. These ensure that logging happens and metadata is loaded up\n", + "# properly.\n", + "try:\n", + " completion = await async_client.beta.chat.completions.parse(**completion_args)\n", + "\n", + " # This helper looks for any error-like situations (the model refuses to\n", + " # answer, content filters, incomplete responses) and throws exceptions that\n", + " # are handled by the next helper. The first argument is an identifier that\n", + " # will be used for logs and metadata namespacing.\n", + " validate_completion(completion)\n", + " logger.debug(\"completion response.\", extra=add_serializable_data(completion))\n", + " metadata[\"completion\"] = completion.model_dump()\n", + "\n", + "except Exception as e:\n", + " # This helper processes all the types of error conditions you might get from\n", + " # the OpenAI API in a standardized way.\n", + " completion_error = CompletionError(e)\n", + " print(completion_error)\n", + " print(completion_error.body)\n", + "\n", + "else:\n", + " # The message_string helper is used to extract the response from the completion\n", + " # (which can get tedious).\n", + " print(message_content_from_completion(completion))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Output types" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### JSON output" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"thoughts\": \"The future of AI is expected to be dynamic and transformative, impacting various sectors from healthcare to education. There is potential for AI to enhance productivity, improve decision-making, and create more personalized experiences.\",\n", + " \"answer\": \"AI is likely to become more integrated into everyday life, leading to smarter cities, more efficient industries, and advances in fields such as medicine and autonomous systems. Ethical considerations will be crucial as AI becomes more powerful, and there will be ongoing discussions about privacy, bias, and the job market. Overall, the future of AI promises significant benefits if its development is guided responsibly.\"\n", + "}\n" + ] + } + ], + "source": [ + "from context import Context\n", + "from openai_client.errors import CompletionError, validate_completion\n", + "\n", + "from openai_client.logging import make_completion_args_serializable, add_serializable_data\n", + "from openai_client.completion import message_content_dict_from_completion, JSON_OBJECT_RESPONSE_FORMAT\n", + "\n", + "context = Context(\"conversation-id-1002\")\n", + "metadata = {}\n", + "completion_args = {\n", + " \"model\": model,\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"system\",\n", + " \"content\": \"You are a famous computer scientist. You are giving a talk at a conference. You are talking about the future of AI and how it will change the world. You are asked a questions by audience members and return your answer as valid JSON like { \\\"thoughts\\\": , \\\"answer\\\": }.\",\n", + " },\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"What is the future of AI?\",\n", + " }\n", + " ],\n", + " \"response_format\": JSON_OBJECT_RESPONSE_FORMAT,\n", + "}\n", + "metadata[\"completion_args\"] = make_completion_args_serializable(completion_args)\n", + "try:\n", + " completion = await async_client.beta.chat.completions.parse(**completion_args)\n", + " validate_completion(completion)\n", + " metadata[\"completion\"] = completion.model_dump()\n", + "except Exception as e:\n", + " completion_error = CompletionError(e)\n", + " metadata[\"completion_error\"] = completion_error.body\n", + " logger.error(completion_error.message, extra=add_serializable_data({\"error\": completion_error.body, \"metadata\": metadata}))\n", + "else:\n", + " message = message_content_dict_from_completion(completion)\n", + " print(json.dumps(message, indent=2))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Structured output\n", + "\n", + "Any Pydantic BaseModel can be used as the \"response_format\" and OpenAI will try to load it up for you." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"thoughts\": \"AI is already having a profound impact across various sectors, and its influence will only grow in the future as technologies continue to advance. We are likely to see significant developments in AI applications, enhanced human-AI collaboration, and ethical considerations around the technology's usage.\",\n", + " \"answer\": \"The future of AI holds tremendous potential to revolutionize how we live, work, and interact. In the coming years, we can expect AI to become increasingly integrated into our daily lives through smarter personal assistants, more efficient business processes, and advancements in healthcare, transportation, and communication technologies. AI will enable more personalized and anticipatory services, where systems proactively assist users based on their preferences and behaviors.\\n\\nFurthermore, AI will drive innovation by augmenting human capabilities, allowing us to tackle complex problems with greater efficiency. This collaboration between humans and machines could lead to breakthroughs in fields like medicine, environmental science, and education.\\n\\nHowever, with these opportunities come challenges. Ethical considerations, such as bias, privacy concerns, and the impact on employment, must be addressed. Developers and policymakers will need to work together to create guidelines and standards that ensure AI technologies are used responsibly and inclusively.\\n\\nAdditionally, explainability and transparency in AI systems will become increasingly important, as stakeholders will demand to understand how these technologies make decisions that affect them.\\n\\nOverall, the future of AI is bright and full of potential, but it requires a careful balancing act to ensure it benefits society as a whole.\"\n", + "}\n" + ] + } + ], + "source": [ + "from context import Context\n", + "from pydantic import BaseModel\n", + "from typing import cast\n", + "from openai_client.errors import CompletionError, validate_completion\n", + "from openai_client.logging import add_serializable_data, make_completion_args_serializable\n", + "\n", + "\n", + "class Output(BaseModel):\n", + " thoughts: str\n", + " answer: str\n", + "\n", + "context = Context(\"conversation-id-1002\")\n", + "metadata = {}\n", + "completion_args = {\n", + " \"model\": model,\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"system\",\n", + " \"content\": \"You are a famous computer scientist. You are giving a talk at a conference. You are talking about the future of AI and how it will change the world. You are asked a questions by audience members and return your thoughtful answer.\",\n", + " },\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"What is the future of AI?\",\n", + " }\n", + " ],\n", + " \"response_format\": Output,\n", + "}\n", + "\n", + "metadata[\"completion_args\"] = make_completion_args_serializable(completion_args)\n", + "try:\n", + " completion = await async_client.beta.chat.completions.parse(**completion_args)\n", + " validate_completion(completion)\n", + " metadata[\"completion\"] = completion.model_dump()\n", + "except Exception as e:\n", + " completion_error = CompletionError(e)\n", + " metadata[\"completion_error\"] = completion_error.body\n", + " logger.error(completion_error.message, extra=add_serializable_data({\"error\": completion_error.body, \"metadata\": metadata}))\n", + "else:\n", + " # The parsed message is in the `parsed` attribute.\n", + " output = cast(Output, completion.choices[0].message.parsed)\n", + " print(output.model_dump_json(indent=2))\n", + "\n", + " # Or you can just get the text of the message like usual.\n", + " # print(completion.choices[0].message.content)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Tools" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Simple tool usage\n", + "\n", + "The OpenAI chat completions API used the idea of \"tools\" to let the model request running a local tool and then processing the output. To use it, you need to create a JSON Schema representation of the function you want to use as a tool, check the response for the model requesting to run that function, run the function, and give the model the results of the function run for a final call.\n", + "\n", + "While you can continue doing all of this yourself, our `complete_with_tool_calls` helper function makes this all easier for you.\n", + "\n", + "Instead of generating JSON schema and executing functions yourself, you can use our `ToolFunctions` class to define the functions you want to be used." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The square of 53 is 2809.\n" + ] + } + ], + "source": [ + "from pydantic import Field\n", + "from openai_client.errors import CompletionError, validate_completion\n", + "from openai_client.tools import complete_with_tool_calls, ToolFunctions, ToolFunction\n", + "\n", + "\n", + "def square_the_number(number: int) -> int:\n", + " \"\"\"\n", + " Return the square of the number.\n", + " \"\"\"\n", + " return number * number\n", + "\n", + "\n", + "tool_functions = ToolFunctions([\n", + " ToolFunction(square_the_number),\n", + "])\n", + "\n", + "metadata = {}\n", + "completion_args = {\n", + " \"model\": model,\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"system\",\n", + " \"content\": \"You are an assistant.\",\n", + " },\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"What's the square of 53?\",\n", + " }\n", + " ],\n", + "}\n", + "\n", + "try:\n", + " completion, new_messages = await complete_with_tool_calls(async_client, completion_args, tool_functions, metadata)\n", + " validate_completion(completion)\n", + "except Exception as e:\n", + " completion_error = CompletionError(e)\n", + " metadata[\"completion_error\"] = completion_error.body\n", + " print(completion_error.message)\n", + " print(completion_error.body)\n", + " print(json.dumps(metadata, indent=2))\n", + "else:\n", + " if completion:\n", + " print(completion.choices[0].message.content)\n", + " # print(json.dumps(metadata, indent=2))\n", + " else:\n", + " print(\"No completion returned.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Structured tool inputs and output\n", + "\n", + "You can use Pydantic models as input to tool function arguments.\n", + "\n", + "You can also tell the model to respond with structured JSON or Pydantic model structures.\n", + "\n", + "Here's an example of doing both things." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"thoughts\": \"I used a function to obtain current weather data for the specified ZIP code, which is 90210 (Beverly Hills, CA). The weather is typically warm and sunny during this season.\",\n", + " \"answer\": \"The current weather in 90210 (Beverly Hills, CA) is sunny with a temperature of 25°C (77°F), and the cloud cover is minimal at 20%. Perfect for outdoor activities!\"\n", + "}\n" + ] + } + ], + "source": [ + "from pydantic import BaseModel\n", + "from typing import cast\n", + "from openai_client.errors import CompletionError, validate_completion\n", + "from openai_client.tools import complete_with_tool_calls, ToolFunctions, ToolFunction\n", + "\n", + "\n", + "class Input(BaseModel):\n", + " zipcode: str\n", + "\n", + "class Weather(BaseModel):\n", + " description: str = Field(description=\"The weather description.\")\n", + " cloud_cover: float\n", + " temp_c: float\n", + " temp_f: float\n", + "\n", + "def get_weather(input: Input) -> Weather:\n", + " \"\"\"Return the weather.\"\"\"\n", + " return Weather(description=\"Sunny\", cloud_cover=0.2, temp_c=25.0, temp_f=77.0)\n", + "\n", + "class Output(BaseModel):\n", + " thoughts: str\n", + " answer: str\n", + "\n", + "metadata = {}\n", + "completion_args = {\n", + " \"model\": model,\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"system\",\n", + " \"content\": \"You are an assistant.\",\n", + " },\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"what is the weather in 90210?\",\n", + " }\n", + " ],\n", + " \"response_format\": Output,\n", + "}\n", + "\n", + "functions = ToolFunctions([\n", + " ToolFunction(get_weather),\n", + "])\n", + "\n", + "try:\n", + " completion, new_messages = await complete_with_tool_calls(async_client, completion_args, functions, metadata)\n", + " validate_completion(completion)\n", + "except Exception as e:\n", + " completion_error = CompletionError(e)\n", + " metadata[\"completion_error\"] = completion_error.body\n", + " print(completion_error.message)\n", + " print(completion_error.body)\n", + " print(json.dumps(metadata, indent=2))\n", + "else:\n", + " if completion:\n", + " # The parsed message is in the `parsed` attribute.\n", + " output = cast(Output, completion.choices[0].message.parsed)\n", + " print(output.model_dump_json(indent=2))\n", + " else:\n", + " print(\"No completion returned.\")\n", + "\n", + " # Or you can just get the text of the message like usual.\n", + " # print(completion.choices[0].message.content)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Tool functions with shadowed locals\n", + "\n", + "If you want to make a local function available to be run as a chat completion\n", + "tool, you can. However, oftentimes, the local function might have some extra\n", + "arguments that you don't want the model to have to fill out for you in a tool\n", + "call. In this case, you can create a wrapper function that has the same\n", + "signature as the tool call and then calls the local function with the extra\n", + "arguments filled in." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The fish calculation of 53 results in the binary number `0b101011111001`. If you need any further analysis or conversion, feel free to ask!\n" + ] + } + ], + "source": [ + "from openai_client.errors import CompletionError, validate_completion\n", + "from openai_client.tools import complete_with_tool_calls, ToolFunctions, ToolFunction\n", + "\n", + "# Here is the real function that does the work.\n", + "def real_square_the_number(number: int, binary: bool = True) -> str:\n", + " \"\"\"\n", + " Return the square of the number.\n", + " \"\"\"\n", + " if binary:\n", + " return bin(number * number)\n", + " return str(number * number)\n", + "\n", + "\n", + "# Here is the wrapper function that whose signature will be used as the tool\n", + "# call. You can just have it calls the real function with the extra arguments\n", + "# filled in.\n", + "def fish_calc(number: int) -> str:\n", + " \"\"\"\n", + " Return the square of the number.\n", + " \"\"\"\n", + " return real_square_the_number(number, binary=True)\n", + "\n", + "# Add then just add wrapper to the tool functions you pass to the\n", + "# `complete_with_tool_calls` function. This is a way you can expose _any_\n", + "# function to be called by the model, but with the args you want the model to\n", + "# fill in!\n", + "tool_functions = ToolFunctions([\n", + " ToolFunction(fish_calc),\n", + "])\n", + "\n", + "metadata = {}\n", + "completion_args = {\n", + " \"model\": model,\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"system\",\n", + " \"content\": \"You are an assistant.\",\n", + " },\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"Run a fish calculation on 53 for me.\",\n", + " }\n", + " ],\n", + "}\n", + "\n", + "try:\n", + " completion, new_messages = await complete_with_tool_calls(async_client, completion_args, tool_functions, metadata)\n", + " validate_completion(completion)\n", + "except Exception as e:\n", + " completion_error = CompletionError(e)\n", + " metadata[\"completion_error\"] = completion_error.body\n", + " print(completion_error.message)\n", + " print(completion_error.body)\n", + " print(json.dumps(metadata, indent=2))\n", + "else:\n", + " if completion:\n", + " print(completion.choices[0].message.content)\n", + " # print(json.dumps(metadata, indent=2))\n", + " else:\n", + " print(\"No completion returned.\")\n" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -218,342 +795,210 @@ "\n", "Our chat driver provides:\n", "\n", - "- The ability to almost magically register functions to the function tool using a `FunctionRegistry`.\n", - "- Tracking of message history.\n", - "- Management of a `Context` object that can be used for session management and supply additional context to functions.\n", - "- Some prompt creation helpers.\n", - "- Other utilities... this is just meant to be an interface you can use to forget about all the api complexities." + "- The ability to almost magically register functions to the function tool.\n", + "- Tracking of message history using in-memory, local, or custom message providers.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Here is the simplest usage of a chat driver\n", + "\n", + "Notice that a .data directory is created by default. This is where the conversation history is stored." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The future of AI is incredibly exciting and holds the potential to fundamentally transform various aspects of our lives, industries, and societies. First and foremost, we're seeing AI become increasingly integrated into our daily routines, from virtual assistants that help manage our schedules to smart home devices that optimize our living environments.\n", + "\n", + "In healthcare, AI will continue to revolutionize the industry with advancements in personalized medicine, predictive analytics, and automated diagnostics, leading to more efficient and accurate treatment options. In areas like finance, AI can enhance decision-making through better risk assessment and fraud detection.\n", + "\n", + "The future of AI also includes significant advancements in natural language processing and computer vision, which will drive innovations in autonomous vehicles, intelligent virtual agents, and other fields requiring human-like perception and interaction.\n", + "\n", + "Moreover, AI will play a crucial role in addressing global challenges such as climate change, by analyzing vast amounts of environmental data to develop more sustainable practices and technologies. In education, AI can offer personalized learning experiences tailored to the needs of each student, enhancing the accessibility and quality of education globally.\n", + "\n", + "However, along with these prospects come important ethical considerations surrounding privacy, data security, and algorithmic bias. It is essential for us as a community to ensure that AI is developed and deployed responsibly, with a focus on transparency and inclusivity.\n", + "\n", + "Finally, as AI continues to evolve, it could also lead to significant changes in the workforce, reshaping job markets and requiring us to rethink how education and skill development align with future needs.\n", + "\n", + "Ultimately, the future of AI is likely to be characterized by a delicate balance between innovation and ethical stewardship, necessitating collaboration across disciplines, industries, and borders to maximize benefits while minimizing potential risks.\n" + ] + } + ], + "source": [ + "from openai_client.chat_driver import ChatDriver, ChatDriverConfig\n", + "\n", + "instructions = \"You are a famous computer scientist. You are giving a talk at a conference. You are talking about the future of AI and how it will change the world. You are asked a questions by audience members.\"\n", + "\n", + "chat_driver = ChatDriver(\n", + " ChatDriverConfig(\n", + " openai_client=async_client,\n", + " model=model,\n", + " instructions=instructions,\n", + " ),\n", + ")\n", + "\n", + "message_event = await chat_driver.respond(\"What is the future of AI?\")\n", + "print(message_event.message)\n", + "# print(message_event.model_dump_json(indent=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### You can register functions to chat drivers\n", + "\n", + "Chat drivers will use any functions you give it as both OpenAI tool calls, and as commands.\n", + "\n", + "With each response call, you can specify what type of response you want to have... string, dictionary, or Pydantic model." + ] + }, + { + "cell_type": "code", + "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "conversation-id-1000\n", "\n", - "Hello, Paul! How can I assist you today?\n", + "Hello Paul! How can I assist you today?\n", "\n", "Commands:\n", - "help(): Return this help message.\n", - "erase(name: str): Erases a stored value.\n", "echo(text: str): Return the text.\n", + "erase(name: str): Erases a stored value.\n", "get_file_contents(file_path: str): Return the contents of a file.\n", "\n", - "conversation-id-1000: Hi, my name is Paul.\n", + "Args:\n", + "- file_path: The path to the file.\n", + "get_weather(input: Input): Return the weather.\n", + "help(): Return this help message.\n", + "json_thing(): Return json.\n", "\n", - "The contents of the file 123.txt are: \"The purpose of life is to be happy.\" If you need any further assistance or information, feel free to ask!\n", + "Echoing: Echo this.\n", "\n", - "{\n", - " \"id\": \"0222a092-6bfd-4523-9cf8-d895070c554b\",\n", - " \"session_id\": null,\n", - " \"timestamp\": \"2024-10-10T17:20:00.290438\",\n", - " \"message\": \"The contents of the file 123.txt are: \\\"The purpose of life is to be happy.\\\" If you need any further assistance or information, feel free to ask!\",\n", - " \"metadata\": {\n", - " \"completion_args\": {\n", - " \"messages\": [\n", - " {\n", - " \"role\": \"system\",\n", - " \"content\": \"You are a helpful assistant.\"\n", - " },\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": \"Hi, my name is Paul.\"\n", - " },\n", - " {\n", - " \"content\": \"Hello, Paul! How can I assist you today?\",\n", - " \"refusal\": null,\n", - " \"role\": \"assistant\",\n", - " \"function_call\": null,\n", - " \"tool_calls\": null\n", - " },\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": \"Please tell me what's in file 123.txt.\"\n", - " }\n", - " ],\n", - " \"model\": \"gpt-4o\",\n", - " \"tools\": [\n", - " {\n", - " \"type\": \"function\",\n", - " \"function\": {\n", - " \"name\": \"help\",\n", - " \"description\": \"Return this help message.\",\n", - " \"parameters\": {\n", - " \"type\": \"object\",\n", - " \"properties\": {}\n", - " }\n", - " }\n", - " },\n", - " {\n", - " \"type\": \"function\",\n", - " \"function\": {\n", - " \"name\": \"erase\",\n", - " \"description\": \"Erases a stored value.\",\n", - " \"parameters\": {\n", - " \"type\": \"object\",\n", - " \"properties\": {\n", - " \"name\": {\n", - " \"type\": \"string\"\n", - " }\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"name\"\n", - " ]\n", - " }\n", - " },\n", - " {\n", - " \"type\": \"function\",\n", - " \"function\": {\n", - " \"name\": \"echo\",\n", - " \"description\": \"Return the text.\",\n", - " \"parameters\": {\n", - " \"type\": \"object\",\n", - " \"properties\": {\n", - " \"text\": {\n", - " \"type\": \"string\"\n", - " }\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"text\"\n", - " ]\n", - " }\n", - " },\n", - " {\n", - " \"type\": \"function\",\n", - " \"function\": {\n", - " \"name\": \"get_file_contents\",\n", - " \"description\": \"Return the contents of a file.\",\n", - " \"parameters\": {\n", - " \"type\": \"object\",\n", - " \"properties\": {\n", - " \"file_path\": {\n", - " \"type\": \"string\"\n", - " }\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"file_path\"\n", - " ]\n", - " }\n", - " }\n", - " ],\n", - " \"response_format\": {\n", - " \"type\": \"text\"\n", - " }\n", - " },\n", - " \"completion_response\": {\n", - " \"id\": \"chatcmpl-AGr7Pmberc78CWRv24nypSR0ALAJv\",\n", - " \"choices\": [\n", - " {\n", - " \"finish_reason\": \"tool_calls\",\n", - " \"index\": 0,\n", - " \"logprobs\": null,\n", - " \"message\": {\n", - " \"content\": null,\n", - " \"refusal\": null,\n", - " \"role\": \"assistant\",\n", - " \"function_call\": null,\n", - " \"tool_calls\": [\n", - " {\n", - " \"id\": \"call_Nbica1hw3v0FSis7aUPQ5Zvy\",\n", - " \"function\": {\n", - " \"arguments\": \"{\\\"file_path\\\":\\\"123.txt\\\"}\",\n", - " \"name\": \"get_file_contents\"\n", - " },\n", - " \"type\": \"function\"\n", - " }\n", - " ]\n", - " }\n", - " }\n", - " ],\n", - " \"created\": 1728580799,\n", - " \"model\": \"gpt-4o-mini\",\n", - " \"object\": \"chat.completion\",\n", - " \"service_tier\": null,\n", - " \"system_fingerprint\": \"fp_878413d04d\",\n", - " \"usage\": {\n", - " \"completion_tokens\": 17,\n", - " \"prompt_tokens\": 136,\n", - " \"total_tokens\": 153,\n", - " \"completion_tokens_details\": null,\n", - " \"prompt_tokens_details\": null\n", - " }\n", - " },\n", - " \"assistant_response\": {\n", - " \"content\": null,\n", - " \"refusal\": null,\n", - " \"role\": \"assistant\",\n", - " \"function_call\": null,\n", - " \"tool_calls\": [\n", - " {\n", - " \"id\": \"call_Nbica1hw3v0FSis7aUPQ5Zvy\",\n", - " \"function\": {\n", - " \"arguments\": \"{\\\"file_path\\\":\\\"123.txt\\\"}\",\n", - " \"name\": \"get_file_contents\"\n", - " },\n", - " \"type\": \"function\"\n", - " }\n", - " ]\n", - " },\n", - " \"function_call_result_message\": {\n", - " \"role\": \"tool\",\n", - " \"content\": \"The purpose of life is to be happy.\",\n", - " \"tool_call_id\": \"call_Nbica1hw3v0FSis7aUPQ5Zvy\"\n", - " },\n", - " \"assistant_tool_completion_args\": {\n", - " \"messages\": [\n", - " {\n", - " \"role\": \"system\",\n", - " \"content\": \"You are a helpful assistant.\"\n", - " },\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": \"Hi, my name is Paul.\"\n", - " },\n", - " {\n", - " \"content\": \"Hello, Paul! How can I assist you today?\",\n", - " \"refusal\": null,\n", - " \"role\": \"assistant\",\n", - " \"function_call\": null,\n", - " \"tool_calls\": null\n", - " },\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": \"Please tell me what's in file 123.txt.\"\n", - " },\n", - " {\n", - " \"content\": null,\n", - " \"refusal\": null,\n", - " \"role\": \"assistant\",\n", - " \"function_call\": null,\n", - " \"tool_calls\": [\n", - " {\n", - " \"id\": \"call_Nbica1hw3v0FSis7aUPQ5Zvy\",\n", - " \"function\": {\n", - " \"arguments\": \"{\\\"file_path\\\":\\\"123.txt\\\"}\",\n", - " \"name\": \"get_file_contents\"\n", - " },\n", - " \"type\": \"function\"\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"role\": \"tool\",\n", - " \"content\": \"The purpose of life is to be happy.\",\n", - " \"tool_call_id\": \"call_Nbica1hw3v0FSis7aUPQ5Zvy\"\n", - " }\n", - " ],\n", - " \"model\": \"gpt-4o\"\n", - " },\n", - " \"assistant_tool_response\": {\n", - " \"content\": \"The contents of the file 123.txt are: \\\"The purpose of life is to be happy.\\\" If you need any further assistance or information, feel free to ask!\",\n", - " \"refusal\": null,\n", - " \"role\": \"assistant\",\n", - " \"function_call\": null,\n", - " \"tool_calls\": null\n", - " }\n", - " }\n", - "}\n" + "The contents of \"123.txt\" are: \"The purpose of life is to be happy.\" How else can I help you?\n", + "\n", + "{\"description\":\"Sunny\",\"cloud_cover\":0.2,\"temp_c\":25.0,\"temp_f\":77.0}\n" ] } ], "source": [ - "from typing import cast\n", - "from chat_driver import ChatDriver, ChatDriverConfig, Context\n", - "from chat_driver import LocalMessageHistoryProvider #, LocalMessageHistoryProviderConfig\n", - "# from typing import List\n", - "# from openai.types.chat import ChatCompletionMessageParam\n", + "from typing import Any, cast\n", + "from openai_client.chat_driver import ChatDriver, ChatDriverConfig\n", + "from openai_client.chat_driver import LocalMessageHistoryProvider\n", + "from pydantic import BaseModel, Field\n", + "from openai_client.tools import ToolFunctions, ToolFunction\n", "\n", "\n", "# When an chat driver is created, it will automatically create a context with a\n", "# session_id. Or, if you want to use a specific session_id, you can pass it as\n", "# an argument. This is useful for scoping this chat driver instance to an\n", "# external identifier.\n", - "context = Context(\"conversation-id-1000\")\n", + "session_id = \"conversation-id-1002\"\n", "\n", "\n", - "# Define tool functions for the chat driver. All functions used by the chat driver\n", - "# require a session_id as the first argument.\n", - "def get_file_contents(context: Context, file_path: str) -> str:\n", - " \"\"\"Return the contents of a file.\"\"\"\n", + "# Define tool functions for the chat driver.\n", + "def get_file_contents(file_path: str) -> str:\n", + " \"\"\"\n", + " Return the contents of a file.\n", + "\n", + " Args:\n", + " - file_path: The path to the file.\n", + " \"\"\"\n", " return \"The purpose of life is to be happy.\"\n", "\n", "\n", - "def erase(context: Context, name: str) -> str:\n", + "def erase(name: str) -> str:\n", " \"\"\"Erases a stored value.\"\"\"\n", " return f\"{context.session_id}: {name} erased\"\n", "\n", + "def json_thing() -> dict[str, Any]:\n", + " \"\"\"Return json.\"\"\"\n", + " return {\"key\": \"value\"}\n", + "\n", + "class Input(BaseModel):\n", + " zipcode: str\n", + "\n", + "class Weather(BaseModel):\n", + " description: str = Field(description=\"The weather description.\")\n", + " cloud_cover: float\n", + " temp_c: float\n", + " temp_f: float\n", + "\n", + "def get_weather(input: Input) -> Weather:\n", + " \"\"\"Return the weather.\"\"\"\n", + " return Weather(description=\"Sunny\", cloud_cover=0.2, temp_c=25.0, temp_f=77.0)\n", "\n", "# Define the chat driver.\n", "instructions = \"You are a helpful assistant.\"\n", "\n", - "# Define the conversation so far (optional).\n", - "# messages: List[ChatCompletionMessageParam] = []\n", - "# localMessageHistoryConfig = LocalMessageHistoryProviderConfig(f\"./data/{context.session_id}\", messages)\n", - "# message_provider = LocalMessageHistoryProvider(localMessageHistoryConfig)\n", + "all_funcs = [ get_file_contents, erase, json_thing, get_weather ]\n", "\n", "chat_driver = ChatDriver(\n", " ChatDriverConfig(\n", " openai_client=async_client,\n", " model=model,\n", " instructions=instructions,\n", - " context=context,\n", " # message_provider=message_provider,\n", - " commands=[erase], # Commands can be registered when instantiating the chat driver.\n", - " functions=[erase], # Functions can be registered when instantiating the chat driver.\n", + " commands=all_funcs, # Commands can be registered when instantiating the chat driver.\n", + " functions=all_funcs, # Functions can be registered when instantiating the chat driver.\n", " ),\n", ")\n", "\n", - "# Let's clear the data from previous runs.\n", + "# Let's clear the data from previous runs by using a custom message provider.\n", "message_provider = cast(LocalMessageHistoryProvider, chat_driver.message_provider)\n", "message_provider.delete_all()\n", "\n", "\n", "# You can also use the `register_function` decorator to register a function.\n", - "# Remember, all functions used by the chat driver require a session_id as the\n", - "# first argument.\n", "@chat_driver.register_function_and_command\n", - "def echo(context: Context, text: str) -> str:\n", + "def echo(text: str) -> str:\n", " \"\"\"Return the text.\"\"\"\n", - " return f\"{context.session_id}: {text}\"\n", + " return f\"Echoing: {text}\"\n", "\n", "\n", "# You can also register functions manually.\n", "chat_driver.register_function_and_command(get_file_contents)\n", "\n", - "# Ok. Let's see if we got one.\n", - "print(chat_driver.context.session_id)\n", - "\n", "# Let's see if the agent can respond.\n", - "response = await chat_driver.respond(\"Hi, my name is Paul.\")\n", + "message_event = await chat_driver.respond(\"Hi, my name is Paul.\")\n", "print()\n", - "print(response.message)\n", + "print(message_event.message)\n", "\n", "# Help command (shows command available).\n", - "response = await chat_driver.respond(\"/help\")\n", + "message_event = await chat_driver.respond(\"/help\")\n", "print()\n", - "print(response.message)\n", + "print(message_event.message)\n", "\n", "# We can run any function or command directly.\n", - "response = await chat_driver.functions.echo(\"Hi, my name is Paul.\")\n", + "message_event = await chat_driver.functions.echo(\"Echo this.\")\n", "print()\n", - "print(response)\n", + "print(message_event)\n", "\n", "# Let's see if the chat driver has the ability to run it's own registered function.\n", - "response = await chat_driver.respond(\"Please tell me what's in file 123.txt.\")\n", + "message_event = await chat_driver.respond(\"Please tell me what's in file 123.txt.\")\n", "print()\n", - "print(response.message)\n", + "print(message_event.message)\n", "\n", - "# Let's see the full response event.\n", + "# Stuctured output.\n", + "message_event = await chat_driver.respond(\"What is the weather in 90210?\", response_format=Weather)\n", "print()\n", - "print(response.to_json())" + "print(message_event.message)\n", + "\n", + "# Let's see the full response event.\n", + "# print()\n", + "# print(response.to_json())" ] }, { @@ -565,28 +1010,43 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User: Hi!\n", + "Assistant: Hello! How can I assist you today?\n", + "User: What's the capital of America?\n", + "Assistant: The capital of the United States of America is Washington, D.C.\n", + "User: No, I meant South America.\n", + "Assistant: South America is a continent composed of multiple countries, each with its own capital. Could you specify which country's capital you're interested in within South America?\n", + "User: Mexico.\n", + "Assistant: Mexico is actually part of North America. The capital of Mexico is Mexico City.\n", + "User: Brazil.\n", + "Assistant: The capital of Brazil is Brasília.\n" + ] + } + ], "source": [ - "from typing import Any\n", - "from chat_driver import ChatDriverConfig, ChatDriver\n", + "from openai_client.chat_driver import ChatDriverConfig, ChatDriver\n", "from context import Context\n", - "\n", - "context = Context(\"conversation-id-1001\")\n", + "from openai_client.tools import ToolFunction, ToolFunctions\n", "\n", "\n", - "def get_file_contents(context: Context, file_path: str) -> str:\n", + "def get_file_contents(file_path: str) -> str:\n", " \"\"\"Returns the contents of a file.\"\"\"\n", " return \"The purpose of life is to be happy.\"\n", "\n", "\n", - "def erase(context: Context, name: str) -> str:\n", + "def erase(name: str) -> str:\n", " \"\"\"Erases a stored value.\"\"\"\n", - " return f\"{context.session_id}: {name} erased\"\n", + " return f\"{session_id}: {name} erased\"\n", "\n", "\n", - "def echo(context: Context, value: Any) -> str:\n", + "def echo(value: str) -> str: # noqa: F811\n", " \"\"\"Echos a value as a string.\"\"\"\n", " match value:\n", " case str():\n", @@ -600,17 +1060,13 @@ " case _:\n", " return str(value)\n", "\n", - "\n", - "functions = [get_file_contents, erase, echo]\n", - "\n", "# Define the chat driver.\n", "chat_driver_config = ChatDriverConfig(\n", " openai_client=async_client,\n", " model=model,\n", " instructions=\"You are an assistant that has access to a sand-boxed Posix shell.\",\n", - " context=context,\n", - " commands=functions,\n", - " functions=functions,\n", + " commands=[ get_file_contents, erase, echo ],\n", + " functions=[ get_file_contents, erase, echo ],\n", ")\n", "\n", "chat_driver = ChatDriver(chat_driver_config)\n", @@ -623,10 +1079,14 @@ " if message == \"\":\n", " break\n", " print(f\"User: {message}\", flush=True)\n", - " response = await chat_driver.respond(message)\n", - " # You can print the entire response event! \n", + " message_event = await chat_driver.respond(message)\n", + " if message_event.metadata.get(\"error\"):\n", + " print(f\"Error: {message_event.metadata.get('error')}\")\n", + " print(message_event.to_json())\n", + " continue\n", + " # You can print the entire message event! \n", " # print(response.to_json())\n", - " print(f\"Assistant: {response.message}\", flush=True)" + " print(f\"Assistant: {message_event.message}\", flush=True)" ] }, { @@ -644,25 +1104,21 @@ "source": [ "from io import BytesIO\n", "from typing import Any, BinaryIO\n", - "from chat_driver import ChatDriverConfig, ChatDriver, ChatDriverConfig\n", + "from openai_client.chat_driver import ChatDriverConfig, ChatDriver, ChatDriverConfig\n", "from context import Context\n", "from assistant_drive import Drive, DriveConfig, IfDriveFileExistsBehavior \n", "\n", - "session_id = \"conversation-id-1001\"\n", - "\n", - "context = Context(session_id)\n", - "\n", "def get_drive_from_context(context):\n", " return Drive(DriveConfig(root=f\".data/drive/{context.session_id}\"))\n", "\n", - "def write_file_contents(context: Context, file_path: str, contents: str) -> str:\n", + "def write_file_contents(file_path: str, contents: str) -> str:\n", " \"\"\"Writes the contents to a file.\"\"\"\n", " drive = get_drive_from_context(context)\n", " content_bytes: BinaryIO = BytesIO(contents.encode(\"utf-8\"))\n", " drive.write(content_bytes, file_path, if_exists=IfDriveFileExistsBehavior.OVERWRITE)\n", " return f\"{file_path} updated.\"\n", "\n", - "def read_file_contents(context: Context, file_path: str) -> str:\n", + "def read_file_contents(file_path: str) -> str:\n", " \"\"\"Returns the contents of a file.\"\"\"\n", " drive = get_drive_from_context(context)\n", " with drive.open_file(file_path) as file:\n", @@ -675,7 +1131,6 @@ " openai_client=async_client,\n", " model=model,\n", " instructions=\"You are an assistant that has access to a sand-boxed Posix shell.\",\n", - " context=context,\n", " commands=functions,\n", " functions=functions,\n", ")\n", @@ -690,10 +1145,10 @@ " if message == \"\":\n", " break\n", " print(f\"User: {message}\", flush=True)\n", - " response = await chat_driver.respond(message)\n", + " message_event = await chat_driver.respond(message)\n", " # You can print the entire response event! \n", " # print(response.to_json())\n", - " print(f\"Assistant: {response.message}\", flush=True)" + " print(f\"Assistant: {message_event.message}\", flush=True)" ] } ], diff --git a/libraries/python/skills/notebooks/notebooks/skills.ipynb b/libraries/python/skills/notebooks/notebooks/skills.ipynb index b1d106df..84e0d740 100644 --- a/libraries/python/skills/notebooks/notebooks/skills.ipynb +++ b/libraries/python/skills/notebooks/notebooks/skills.ipynb @@ -128,13 +128,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['chat_driver', 'logs.jsonl', 'test.txt']\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User: /help\n", + "Posix skill: Commands:\n", + "cd(directory: str): Change the current working directory.\n", + "help(): Return this help message.\n", + "ls(path: str = \".\"): List directory contents.\n", + "mkdir(dirname: str): Create a new directory.\n", + "mv(src: str, dest: str): Move a file or directory.\n", + "pwd(): Return the current directory.\n", + "read_file(filename: str): Read the contents of a file.\n", + "rm(path: str): Remove a file or directory.\n", + "run_command(command: str): Run a shell command in the current directory.\n", + "touch(filename: str): Create an empty file.\n", + "write_file(filename: str, content: str): Write content to a file.\n" + ] + } + ], "source": [ "from pathlib import Path\n", "from posix_skill import PosixSkill\n", - "from chat_driver import ChatDriverConfig\n", + "from openai_client.chat_driver import ChatDriverConfig\n", "from context import Context\n", "\n", "context = Context(\"skills-123.posix\")\n", @@ -144,11 +171,9 @@ " openai_client=async_client,\n", " model=model,\n", " instructions=\"You are an assistant that has access to a sand-boxed Posix shell.\",\n", - " context=context,\n", ")\n", "\n", "posix_skill = PosixSkill(\n", - " context=context,\n", " sandbox_dir=Path(\".data\"),\n", " chat_driver_config=chat_driver_config,\n", " mount_dir=\"/mnt/data\",\n", @@ -181,26 +206,38 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User: Hi!\n", + "Assistant: MessageEvent: Hello! How can I assist you today?\n", + "User: /help\n", + "Assistant: MessageEvent: Commands:\n", + "help(): Return this help message.\n", + "list_routines(): Lists all the routines available in the assistant.\n", + "run_routine(name: str, vars: dict[str, typing.Any] | None): Run an assistant routine.\n" + ] + } + ], "source": [ "from pathlib import Path\n", "from skill_library import Assistant\n", - "from chat_driver import ChatDriverConfig\n", + "from openai_client.chat_driver import ChatDriverConfig\n", "from posix_skill import PosixSkill\n", "\n", "\n", "# Define the assistant.\n", - "instructions = \"You are a helpful assistant.\"\n", - "\n", "chat_driver_config = ChatDriverConfig(\n", " openai_client=async_client,\n", " model=model,\n", - " instructions=instructions,\n", + " instructions=\"You are a helpful assistant.\",\n", ")\n", "\n", - "assistant = Assistant(name=\"Alice\", chat_driver_config=chat_driver_config, session_id=\"posix-assistant-123\")\n", + "assistant = Assistant(name=\"Alice\", assistant_id=\"posix-assistant-123\", chat_driver_config=chat_driver_config)\n", "\n", "# Now that the assistant has been created with a context, we can create the\n", "# skills and register them with the assistant's context.\n", @@ -208,7 +245,6 @@ "# Define the posix skill. This skill will be used by the assistant. Note that\n", "# some skills may have a conversational interface using a chat driver.\n", "posix_skill = PosixSkill(\n", - " context=assistant.context,\n", " sandbox_dir=Path(\".data\"),\n", " mount_dir=\"/mnt/data\",\n", " chat_driver_config=ChatDriverConfig(\n", @@ -245,7 +281,7 @@ "source": [ "from pathlib import Path\n", "from skill_library import Assistant\n", - "from chat_driver import ChatDriverConfig\n", + "from openai_client.chat_driver import ChatDriverConfig\n", "from posix_skill import PosixSkill\n", "\n", "# Define the posix skill. This skill will be used by the assistant. Note that\n", @@ -260,11 +296,10 @@ " model=model,\n", " instructions=\"You are a helpful assistant.\",\n", " ),\n", - " session_id=\"assistant-123\",\n", + " assistant_id=\"assistant-123\",\n", ")\n", "\n", "posix_skill = PosixSkill(\n", - " context=assistant.context,\n", " sandbox_dir=Path(\".data\"),\n", " mount_dir=\"/mnt/data\",\n", " chat_driver_config=ChatDriverConfig(\n", @@ -302,7 +337,7 @@ "import nest_asyncio\n", "from pathlib import Path\n", "from skill_library import Assistant\n", - "from chat_driver import ChatDriverConfig\n", + "from openai_client.chat_driver import ChatDriverConfig\n", "from posix_skill import PosixSkill\n", "nest_asyncio.apply()\n", "\n", @@ -310,11 +345,10 @@ "chat_driver_config = ChatDriverConfig(\n", " openai_client=async_client, model=model, instructions=\"You are a helpful assistant.\"\n", ")\n", - "assistant = Assistant(name=\"Alice\", chat_driver_config=chat_driver_config, session_id=\"assistant-123\")\n", + "assistant = Assistant(name=\"Alice\", chat_driver_config=chat_driver_config, assistant_id=\"assistant-123\")\n", "\n", "# Define the posix skill.\n", "posix_skill = PosixSkill(\n", - " context=assistant.context,\n", " sandbox_dir=Path(\".data\"),\n", " mount_dir=\"/mnt/data\",\n", " chat_driver_config=ChatDriverConfig(\n", diff --git a/libraries/python/skills/notebooks/pyproject.toml b/libraries/python/skills/notebooks/pyproject.toml index 7525eca9..15f0120f 100644 --- a/libraries/python/skills/notebooks/pyproject.toml +++ b/libraries/python/skills/notebooks/pyproject.toml @@ -7,10 +7,8 @@ requires-python = ">=3.11" dependencies = [ "assistant-drive>=0.1.0", "azure-identity>=1.17.1", - "chat-driver>=0.1.0", "context>=0.1.0", "events>=0.1.0", - "function-registry>=0.1.0", "nest-asyncio>=1.6.0", "openai>=1.16.1", "openai-client>=0.1.0", @@ -26,10 +24,8 @@ dev-dependencies = [ [tool.uv.sources] assistant-drive = { path = "../../assistant-drive", editable = true } -chat-driver = { path = "../../chat-driver", editable = true } context = { path = "../../context", editable = true } events = { path = "../../events", editable = true } -function-registry = { path = "../../function-registry", editable = true } +openai-client = { path = "../../openai-client", editable = true } posix-skill = { path = "../skills/posix-skill", editable = true } skill-library = { path = "../skill-library/", editable = true } -openai-client = { path = "../../openai-client", editable = true } diff --git a/libraries/python/skills/notebooks/uv.lock b/libraries/python/skills/notebooks/uv.lock index ee75007c..00b5314a 100644 --- a/libraries/python/skills/notebooks/uv.lock +++ b/libraries/python/skills/notebooks/uv.lock @@ -326,39 +326,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "../../chat-driver" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../context" }, - { name = "events", editable = "../../events" }, - { name = "function-registry", editable = "../../function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../../openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - [[package]] name = "click" version = "8.1.7" @@ -606,40 +573,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -1056,7 +989,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -1069,7 +1002,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../function-registry" }, + { name = "events", editable = "../../events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, @@ -1185,17 +1118,17 @@ name = "posix-skill" version = "0.1.0" source = { editable = "../skills/posix-skill" } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../chat-driver" }, { name = "context", editable = "../../context" }, { name = "events", editable = "../../events" }, + { name = "openai-client", editable = "../../openai-client" }, { name = "skill-library", editable = "../skill-library" }, ] @@ -1718,11 +1651,11 @@ name = "skill-library" version = "0.1.0" source = { editable = "../skill-library" } dependencies = [ - { name = "chat-driver" }, + { name = "assistant-drive" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "openai" }, + { name = "openai-client" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -1732,11 +1665,11 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../chat-driver" }, + { name = "assistant-drive", editable = "../../assistant-drive" }, { name = "context", editable = "../../context" }, { name = "events", editable = "../../events" }, - { name = "function-registry", editable = "../../function-registry" }, { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../openai-client" }, { name = "pydantic", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.3.4" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1744,6 +1677,13 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "skill-notebooks" version = "0.1.0" @@ -1751,10 +1691,8 @@ source = { virtual = "." } dependencies = [ { name = "assistant-drive" }, { name = "azure-identity" }, - { name = "chat-driver" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "nest-asyncio" }, { name = "openai" }, { name = "openai-client" }, @@ -1772,10 +1710,8 @@ dev = [ requires-dist = [ { name = "assistant-drive", editable = "../../assistant-drive" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "chat-driver", editable = "../../chat-driver" }, { name = "context", editable = "../../context" }, { name = "events", editable = "../../events" }, - { name = "function-registry", editable = "../../function-registry" }, { name = "nest-asyncio", specifier = ">=1.6.0" }, { name = "openai", specifier = ">=1.16.1" }, { name = "openai-client", editable = "../../openai-client" }, diff --git a/libraries/python/skills/skill-library/.vscode/settings.json b/libraries/python/skills/skill-library/.vscode/settings.json index 29b600d7..1d907497 100644 --- a/libraries/python/skills/skill-library/.vscode/settings.json +++ b/libraries/python/skills/skill-library/.vscode/settings.json @@ -20,7 +20,7 @@ "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.typeCheckingMode": "basic", "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", - "python.testing.pytestEnabled": false, + "python.testing.pytestEnabled": true, "search.exclude": { "**/.venv": true, "**/data": true @@ -52,5 +52,7 @@ "pypdf", "runtimes", "tiktoken" - ] + ], + "python.testing.pytestArgs": ["skill_library"], + "python.testing.unittestEnabled": false } diff --git a/libraries/python/skills/skill-library/README.md b/libraries/python/skills/skill-library/README.md index 7ad9c114..1e58128a 100644 --- a/libraries/python/skills/skill-library/README.md +++ b/libraries/python/skills/skill-library/README.md @@ -25,9 +25,10 @@ more easily: assistants by providing clearer purposeful abstractions and better defining or disambiguating commonly confused terms. For example, we separate out a lot of the complexity of interacting with the OpenAI Chat Completion API with the - [chat driver](../../chat-driver/README.md) abstraction and we now - distinguish between chat commands, chat tool functions, and routine actions in - a clear way, even though they're really all just functions. + [chat driver](../../openai-client/openai_client/chat_driver/README.md) + abstraction and we now distinguish between chat commands, chat tool functions, + and routine actions in a clear way, even though they're really all just + functions. - Routines (formerly referred to as "Recipes") make it clear that what we are developing agents that can automate productive work collaboratively with the user. We have several ideas here, from simply following a set of steps, to diff --git a/libraries/python/skills/skill-library/pyproject.toml b/libraries/python/skills/skill-library/pyproject.toml index 5765b75a..3662f6c5 100644 --- a/libraries/python/skills/skill-library/pyproject.toml +++ b/libraries/python/skills/skill-library/pyproject.toml @@ -6,26 +6,32 @@ authors = [{name="MADE:Explorers"}] readme = "README.md" requires-python = ">=3.11" dependencies = [ + "assistant-drive>=0.1.0", + "context>=0.1.0", + "events>=0.1.0", + "openai-client>=0.1.0", "openai>=1.16.1", - "pydantic>=2.6.1", "pydantic-settings>=2.3.4", + "pydantic>=2.6.1", "python-dotenv>=1.0.1", "requests>=2.32.0", "tiktoken>=0.7.0", - "chat-driver>=0.1.0", - "context>=0.1.0", - "events>=0.1.0", - "function-registry>=0.1.0", ] [tool.uv] package = true +dev-dependencies = [ + "pytest>=8.3.1", + "pytest-asyncio>=0.23.8", + "pytest-repeat>=0.9.3", +] [tool.uv.sources] -chat-driver = { path = "../../chat-driver", editable = true } +assistant-drive = { path = "../../assistant-drive", editable = true } context = { path = "../../context", editable = true } events = { path = "../../events", editable = true } -function-registry = { path = "../../function-registry", editable = true } +openai-client = { path = "../../openai-client", editable = true } + [build-system] requires = ["hatchling"] diff --git a/libraries/python/function-registry/pytest.ini b/libraries/python/skills/skill-library/pytest.ini similarity index 100% rename from libraries/python/function-registry/pytest.ini rename to libraries/python/skills/skill-library/pytest.ini diff --git a/libraries/python/skills/skill-library/skill_library/__init__.py b/libraries/python/skills/skill-library/skill_library/__init__.py index 3a5fe8f8..afe9eecb 100644 --- a/libraries/python/skills/skill-library/skill_library/__init__.py +++ b/libraries/python/skills/skill-library/skill_library/__init__.py @@ -1,12 +1,20 @@ -from .assistant import Assistant +import logging + from context import Context -from .routine import InstructionRoutine, RoutineTypes -from .skill import Skill + +from .assistant import Assistant +from .routine import FunctionRoutine, InstructionRoutine, ProgramRoutine, RoutineTypes +from .skill import EmitterType, Skill + +logger = logging.getLogger(__name__) __all__ = [ "Assistant", "Context", + "EmitterType", + "FunctionRoutine", "InstructionRoutine", + "ProgramRoutine", "RoutineTypes", "Skill", ] diff --git a/libraries/python/skills/skill-library/skill_library/actions.py b/libraries/python/skills/skill-library/skill_library/actions.py new file mode 100644 index 00000000..7350a97f --- /dev/null +++ b/libraries/python/skills/skill-library/skill_library/actions.py @@ -0,0 +1,235 @@ +import ast +import inspect +from dataclasses import dataclass +from typing import Any, Callable + + +@dataclass +class Parameter: + """ + Tool functions are described by their parameters. This dataclass + describes a single parameter of a tool function. + """ + + name: str + type: Any + description: str | None + default_value: Any | None = None + + +class Action: + """ + A tool function is a Python function that can be called as a tool from the + chat completion API. This class wraps a function so you can generate it's + JSON schema for the chat completion API, execute it with arguments, and + generate a usage string (for help messages) + """ + + def __init__(self, fn: Callable, name: str | None = None, description: str | None = None) -> None: + self.fn = fn + self.name = name or fn.__name__ + self.description = description or inspect.getdoc(fn) or self.name.replace("_", " ").title() + + def parameters(self, exclude: list[str] = []) -> list[Parameter]: + """ + This function's parameters and their default values. + """ + parameters = dict(inspect.signature(self.fn).parameters) + for param_name in exclude: + del parameters[param_name] + return [ + Parameter( + name=param_name, + type=param.annotation, + description=None, # param.annotation.description, + default_value=param.default, + ) + for param_name, param in parameters.items() + ] + + def usage(self) -> str: + """ + A usage string for this function. This can be used in help messages. + """ + name = self.name + param_usages = [] + for param in self.parameters(): + param_type = param.type + try: + param_type = param.type.__name__ + except AttributeError: + param_type = param.type + usage = f"{param.name}: {param_type}" + if param.default_value is not inspect.Parameter.empty: + if isinstance(param.default_value, str): + usage += f' = "{param.default_value}"' + else: + usage += f" = {param.default_value}" + param_usages.append(usage) + + description = self.description + return f"{name}({', '.join(param_usages)}): {description}" + + async def execute(self, *args, **kwargs) -> Any: + """ + Run this function, and return its value. If the function is a coroutine, + it will be awaited. If string_response is True, the response will be + converted to a string. + """ + result = self.fn(*args, **kwargs) + if inspect.iscoroutine(result): + result = await result + return result + + +class ActionHandler: + def __init__(self, actions: "Actions") -> None: + self.actions = actions + + def __getattr__(self, name: str) -> Callable: + """Makes registered functions accessible as attributes of the functions object.""" + if name not in self.actions.action_map: + raise AttributeError(f"'Actions' object has no attribute '{name}'") + + async def wrapper(*args, **kwargs) -> Any: + return await self.actions.execute_action(name, args, kwargs) + + return wrapper + + +class Actions: + """ + A set of tool functions that can be called from the Chat Completions API. + Pass this into the `complete_with_tool_calls` helper function to run a full + tool-call completion against the API. + """ + + def __init__(self, actions: list[Action] | None = None, with_help: bool = False) -> None: + # Set up function map. + self.action_map = {} + if actions: + for function in actions: + self.action_map[function.name] = function + + # A help message can be generated for the function map. + if with_help: + self.action_map["help"] = Action(self.help) + + # This allows actions to be called as attributes. + self.functions = ActionHandler(self) + + def help(self) -> str: + """Return this help message.""" + + usage = [f"{command.usage()}" for command in self.action_map.values()] + usage.sort() + return "Commands:\n" + "\n".join(usage) + + def add_function(self, function: Callable, name: str | None = None, description: str | None = None) -> None: + """Register a function as an action.""" + if not name: + name = function.__name__ + self.action_map[name] = Action(function, name, description) + + def add_functions(self, functions: list[Callable]) -> None: + """Register a list of functions as actions.""" + for function in functions: + self.add_function(function) + + def has_action(self, name: str) -> bool: + return name in self.action_map + + def get_action(self, name: str) -> Action | None: + return self.action_map.get(name) + + def get_actions(self) -> list[Action]: + return [function for function in self.action_map.values()] + + async def execute_action( + self, + name: str, + args: tuple = (), + kwargs: dict[str, Any] = {}, + ) -> Any: + """ + Run a function from the ToolFunctions list by name. If string_response + is True, the function return value will be converted to a string. + """ + function = self.get_action(name) + if not function: + raise ValueError(f"Function {name} not found in registry.") + return await function.execute(*args, **kwargs) + + async def execute_action_string(self, function_string: str) -> Any: + """Parse a function string and execute the function.""" + try: + function, args, kwargs = self.parse_action_string(function_string) + except ValueError as e: + raise ValueError(f"{e}. Type: `/help` for more information.") + if not function: + raise ValueError("Function not found in registry. Type: `/help` for more information.") + return await function.execute(*args, **kwargs) + + def parse_action_string(self, action_string: str) -> tuple[Action | None, list[Any], dict[str, Any]]: + """Parse a function call string into a function and its arguments.""" + + # As a convenience, remove any leading slashes. + action_string = action_string.lstrip("/") + + # As a convenience, add parentheses if they are missing. + if " " not in action_string and "(" not in action_string: + action_string += "()" + + # Parse the string into an AST (Abstract Syntax Tree) + try: + tree = ast.parse(action_string) + except SyntaxError: + raise ValueError("Invalid function call. Please check your syntax.") + + # Ensure the tree contains exactly one expression (the function call) + if not (isinstance(tree, ast.Module) and len(tree.body) == 1 and isinstance(tree.body[0], ast.Expr)): + raise ValueError("Expected a single function call.") + + # The function call is stored as a `Call` node within the expression + call_node = tree.body[0].value + if not isinstance(call_node, ast.Call): + raise ValueError("Invalid function call. Please check your syntax.") + + # Extract the function name + if isinstance(call_node.func, ast.Name): + action_name = call_node.func.id + else: + raise ValueError("Unsupported function format. Please check your syntax.") + + # Helper function to evaluate AST nodes to their Python equivalent + def eval_node(node): + if isinstance(node, ast.Constant): + return node.value + elif isinstance(node, ast.List): + return [eval_node(elem) for elem in node.elts] + elif isinstance(node, ast.Tuple): + return tuple(eval_node(elem) for elem in node.elts) + elif isinstance(node, ast.Dict): + return {eval_node(key): eval_node(value) for key, value in zip(node.keys, node.values)} + elif isinstance(node, ast.Name): + return node.id # This can return variable names, but we assume they're constants + elif isinstance(node, ast.BinOp): # Handling arithmetic expressions + return eval(compile(ast.Expression(node), filename="", mode="eval")) + elif isinstance(node, ast.Call): + raise ValueError("Nested function calls are not supported.") + else: + raise ValueError(f"Unsupported AST node type: {type(node).__name__}") + + # Extract positional arguments + args = [eval_node(arg) for arg in call_node.args] + + # Extract keyword arguments + kwargs = {} + for kw in call_node.keywords: + kwargs[kw.arg] = eval_node(kw.value) + + action = self.get_action(action_name) + if not action: + return None, [], {} + + return action, args, kwargs diff --git a/libraries/python/skills/skill-library/skill_library/assistant.py b/libraries/python/skills/skill-library/skill_library/assistant.py index dae7fb53..804d2017 100644 --- a/libraries/python/skills/skill-library/skill_library/assistant.py +++ b/libraries/python/skills/skill-library/skill_library/assistant.py @@ -1,12 +1,23 @@ import asyncio +from os import PathLike from typing import Any, AsyncIterator +from uuid import uuid4 -from chat_driver import TEXT_RESPONSE_FORMAT, ChatDriver, ChatDriverConfig, ResponseFormat -from context import Context +from assistant_drive import Drive, DriveConfig, IfDriveFileExistsBehavior from events import BaseEvent, EventProtocol - -from .routine import InstructionRoutine, ProgramRoutine -from .routine_runners import InstructionRoutineRunner, ProgramRoutineRunner +from openai.types.chat.completion_create_params import ( + ResponseFormat, +) +from openai_client.chat_driver import ( + ChatDriver, + ChatDriverConfig, + LocalMessageHistoryProvider, + LocalMessageHistoryProviderConfig, +) +from openai_client.completion import TEXT_RESPONSE_FORMAT +from openai_client.messages import format_with_liquid + +from .run_context import RunContext from .skill import Skill from .skill_registry import SkillRegistry @@ -15,22 +26,60 @@ class Assistant: def __init__( self, name, + assistant_id: str | None, chat_driver_config: ChatDriverConfig, - session_id: str | None = None, + drive_root: PathLike | None = None, + metadrive_drive_root: PathLike | None = None, + skills: list[Skill] = [], ) -> None: + self.skill_registry: SkillRegistry = SkillRegistry() + self.name = name - self.skill_registry = SkillRegistry() + + if not assistant_id: + assistant_id = str(uuid4()) + + # Configure the assistant chat interface. + if chat_driver_config.message_provider is None: + chat_driver_config.message_provider = LocalMessageHistoryProvider( + LocalMessageHistoryProviderConfig(session_id=assistant_id, formatter=format_with_liquid) + ) self.chat_driver = self._register_chat_driver(chat_driver_config) + + # Set up the assistant event queue. self._event_queue = asyncio.Queue() # Async queue for events self._stopped = asyncio.Event() # Event to signal when the assistant has stopped - self._running_routines = {} - self.context = Context(session_id=session_id, emit=self._emit) + if skills: + self.register_skills(skills) + + # The assistant drive can be used to read and write files to a + # particular location. The assistant drive should be used for + # assistant-specific data and not for general data storage. + self.drive: Drive = Drive( + DriveConfig( + root=drive_root or f".data/{assistant_id}/assistant", + default_if_exists_behavior=IfDriveFileExistsBehavior.OVERWRITE, + ) + ) + + # The assistant run context identifies the assistant session (session) + # and provides necessary utilities to be used for this particular + # assistant session. The run context is passed to the assistant's chat + # driver commands and functions and all skill actions and routines that + # are run by the assistant. + self.run_context = RunContext( + session_id=assistant_id or str(uuid4()), + assistant_drive=self.drive, + emit=self._emit, + run_routine=self.skill_registry.run_routine_by_name, + metadata_drive_root=metadrive_drive_root, + ) ###################################### # Lifecycle and event handling ###################################### - async def wait(self) -> Context: + async def wait(self) -> RunContext: """ After initializing an assistant, call this method to wait for assistant events. While running, any events produced by the assistant can be @@ -39,7 +88,7 @@ async def wait(self) -> Context: """ await self._stopped.wait() - return self.context + return self.run_context def stop(self) -> None: self._stopped.set() # Signal that we are stopping @@ -59,13 +108,35 @@ async def events(self) -> AsyncIterator[EventProtocol]: await asyncio.sleep(0.005) def _emit(self, event: EventProtocol) -> None: - event.session_id = self.context.session_id + event.session_id = self.run_context.session_id self._event_queue.put_nowait(event) async def put_message(self, message: str) -> None: - """Exposed externally for sending messages to the assistant.""" - response = await self.chat_driver.respond(message) - self._emit(response) + """ + Exposed externally for sending messages to the assistant. + + If a routine is currently running, send the message to the routine. + + This is a simple way to handle conversation flows. In the future, we + would like to keep track of multiple topics and allow the assistant to + switch between them. The current idea is that we might treat a topic + like multi-tasking on a Linux system with one topic at a time being + "foreground" and the others being "background". The assistant would + decide when to switch topics based on the content of the messages. A + topic might include a subset of the conversation history, only specific + skills, etc. + + For now, though, we track only a single topic at a time. If a routine is + currently running, we send the message to the routine. Otherwise, we + send the message to the chat driver. + """ + # If a routine is running, send the message to the routine. + if await self.run_context.routine_stack.peek(): + await self.step_active_routine(message) + else: + # Otherwise, send the message to the chat driver. + response = await self.chat_driver.respond(message) + self._emit(response) ###################################### # Chat interface @@ -82,6 +153,7 @@ def _register_chat_driver(self, chat_driver_config: ChatDriverConfig) -> ChatDri "These vars are like the routines' input.\n\n" "Available routines and their available vars: {routines}. " ) + chat_functions = ChatFunctions(self) functions = [chat_functions.list_routines, chat_functions.run_routine] config.commands = functions @@ -121,10 +193,6 @@ def register_skills(self, skills: list[Skill]) -> None: that an assistant uses at the same time so dependencies can be loaded in the correct order.""" self.skill_registry.register_all_skills(skills) - if self.chat_driver: - for skill in skills: - self.chat_driver.register_functions(skill.get_chat_functions()) - self.chat_driver.register_commands(skill.get_chat_commands()) # def list_actions(self, context: Context) -> list[str]: # """Lists all the actions the assistant is able to perform.""" @@ -136,27 +204,13 @@ def list_routines(self) -> list[str]: async def run_routine(self, name: str, vars: dict[str, Any] | None = None) -> Any: """ - Run an assistant routine. This is going to be much of the - magic of the assistant. Currently, is just runs through the - steps of a routine, but this will get much more sophisticated. - It will need to handle configuration, managing results of steps, - handling errors and retries, etc. ALso, this is where we will put - meta-cognitive functions such as having the assistant create a plan - from the routine and executing it dynamically while monitoring progress. + Run an assistant routine by name (e.g. .). """ - skill, routine = self.skill_registry.get_routine(name) - if not skill: - raise ValueError(f"Skill {name} not found.") - if not routine: - raise ValueError(f"Routine {name} not found.") + await self.skill_registry.run_routine_by_name(self.run_context, name, vars) - match routine: - case InstructionRoutine(): - runner = InstructionRoutineRunner(self) - return await runner.run(skill, routine, vars) - case ProgramRoutine(): - runner = ProgramRoutineRunner(self) - return await runner.run(skill, routine, vars) + async def step_active_routine(self, message: str) -> None: + """Run another step in the current routine.""" + await self.skill_registry.step_active_routine(self.run_context, message) class ChatFunctions: @@ -168,11 +222,11 @@ class ChatFunctions: def __init__(self, assistant: Assistant) -> None: self.assistant = assistant - def list_routines(self, context: Context) -> list[str]: + def list_routines(self) -> list[str]: """Lists all the routines available in the assistant.""" return self.assistant.list_routines() - async def run_routine(self, context: Context, name: str, vars: dict[str, Any] | None = None) -> Any: + async def run_routine(self, name: str, vars: dict[str, Any] | None) -> Any: """ Run an assistant routine. """ diff --git a/libraries/python/skills/skill-library/skill_library/routine.py b/libraries/python/skills/skill-library/skill_library/routine.py index 541fa249..2c5ad675 100644 --- a/libraries/python/skills/skill-library/skill_library/routine.py +++ b/libraries/python/skills/skill-library/skill_library/routine.py @@ -1,5 +1,10 @@ -from typing import Union import re +from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional, Union + +from skill_library.run_context import RunContext + +if TYPE_CHECKING: + from skill_library.skill import Skill def find_template_vars(text: str) -> list[str]: @@ -15,9 +20,17 @@ def __init__( self, name: str, description: str, + skill: "Skill", ) -> None: self.name = name self.description = description + self.skill = skill + + def fullname(self) -> str: + return f"{self.skill.name}.{self.name}" + + def __str__(self) -> str: + return self.fullname() class InstructionRoutine(Routine): @@ -26,10 +39,12 @@ def __init__( name: str, description: str, routine: str, + skill: "Skill", ) -> None: super().__init__( name=name, description=description, + skill=skill, ) self.routine = routine @@ -44,10 +59,12 @@ def __init__( name: str, description: str, program: str, + skill: "Skill", ) -> None: super().__init__( name=name, description=description, + skill=skill, ) self.program = program @@ -56,4 +73,25 @@ def __str__(self) -> str: return f"{self.name}(vars: {template_vars}): {self.description}" -RoutineTypes = Union[InstructionRoutine, ProgramRoutine] +class FunctionRoutine(Routine): + def __init__( + self, + name: str, + description: str, + init_function: Callable[[RunContext, Optional[Dict[str, Any]]], Awaitable[None]], + step_function: Callable[[RunContext, Optional[str]], Awaitable[Optional[str]]], + skill: "Skill", + ) -> None: + super().__init__( + name=name, + description=description, + skill=skill, + ) + self.init_function = init_function + self.step_function = step_function + + def __str__(self) -> str: + return f"{self.name}: {self.description}" + + +RoutineTypes = Union[InstructionRoutine, ProgramRoutine, FunctionRoutine] diff --git a/libraries/python/skills/skill-library/skill_library/routine_runners/__init__.py b/libraries/python/skills/skill-library/skill_library/routine_runners/__init__.py index 927ef360..72a64325 100644 --- a/libraries/python/skills/skill-library/skill_library/routine_runners/__init__.py +++ b/libraries/python/skills/skill-library/skill_library/routine_runners/__init__.py @@ -1,8 +1,9 @@ from typing import Union +from .function_routine_runner import FunctionRoutineRunner from .instruction_routine_runner import InstructionRoutineRunner from .program_routine_runner import ProgramRoutineRunner -RunnerTypes = Union[InstructionRoutineRunner, ProgramRoutineRunner] +RunnerTypes = Union[InstructionRoutineRunner, ProgramRoutineRunner, FunctionRoutineRunner] __all__ = ["InstructionRoutineRunner", "RunnerTypes"] diff --git a/libraries/python/skills/skill-library/skill_library/routine_runners/function_routine_runner.py b/libraries/python/skills/skill-library/skill_library/routine_runners/function_routine_runner.py new file mode 100644 index 00000000..f57e09a1 --- /dev/null +++ b/libraries/python/skills/skill-library/skill_library/routine_runners/function_routine_runner.py @@ -0,0 +1,20 @@ +from typing import Any + +from ..routine import FunctionRoutine +from ..run_context import RunContext + + +class FunctionRoutineRunner: + def __init__(self) -> None: + pass + + async def run( + self, context: RunContext, routine: FunctionRoutine, vars: dict[str, Any] | None = None + ) -> Any: + routine.init_function(context, vars) + + async def next(self, context: RunContext, routine: FunctionRoutine, message: str) -> Any: + """ + Run the next step in the current routine. + """ + routine.step_function(context, message) diff --git a/libraries/python/skills/skill-library/skill_library/routine_runners/instruction_routine_runner.py b/libraries/python/skills/skill-library/skill_library/routine_runners/instruction_routine_runner.py index 995f2b59..09a366fc 100644 --- a/libraries/python/skills/skill-library/skill_library/routine_runners/instruction_routine_runner.py +++ b/libraries/python/skills/skill-library/skill_library/routine_runners/instruction_routine_runner.py @@ -1,20 +1,16 @@ -from typing import TYPE_CHECKING, Any +from typing import Any from events import BaseEvent, InformationEvent from ..routine import InstructionRoutine -from ..skill import Skill - -if TYPE_CHECKING: - from ..assistant import Assistant +from ..run_context import RunContext class InstructionRoutineRunner: - def __init__(self, assistant: "Assistant") -> None: - self.skill_registry = assistant.skill_registry - self.context = assistant.context + def __init__(self) -> None: + pass - async def run(self, skill: Skill, routine: InstructionRoutine, vars: dict[str, Any] | None = None) -> Any: + async def run(self, context: RunContext, routine: InstructionRoutine, vars: dict[str, Any] | None = None) -> Any: """ Run an Instruction routine. This just runs through the steps of a routine, sending each one to a skill's response endpoint. Note, this @@ -34,9 +30,15 @@ async def run(self, skill: Skill, routine: InstructionRoutine, vars: dict[str, A steps = routine.routine.split("\n") for step in steps: - self.context.emit(InformationEvent(message=f"Running step: {step}")) - response: BaseEvent = await skill.respond(message=step) + context.emit(InformationEvent(message=f"Running step: {step}")) + response: BaseEvent = await routine.skill.respond(message=step) informationEvent = InformationEvent(**response.model_dump()) - self.context.emit(informationEvent) + context.emit(informationEvent) return + + async def next(self, context: RunContext, routine: InstructionRoutine, message: str) -> Any: + """ + Run the next step in the current routine. + """ + pass diff --git a/libraries/python/skills/skill-library/skill_library/routine_runners/program_routine_runner.py b/libraries/python/skills/skill-library/skill_library/routine_runners/program_routine_runner.py index 6941daf3..4a842828 100644 --- a/libraries/python/skills/skill-library/skill_library/routine_runners/program_routine_runner.py +++ b/libraries/python/skills/skill-library/skill_library/routine_runners/program_routine_runner.py @@ -1,18 +1,16 @@ -from typing import TYPE_CHECKING, Any +from typing import Any from ..routine import ProgramRoutine -from ..skill import Skill - -if TYPE_CHECKING: - from ..assistant import Assistant +from ..run_context import RunContext class ProgramRoutineRunner: - def __init__(self, assistant: "Assistant") -> None: - self.skill_registry = assistant.skill_registry - self.context = assistant.context + def __init__(self) -> None: + pass - async def run(self, skill: Skill, routine: ProgramRoutine, vars: dict[str, Any] | None = None) -> Any: + async def run( + self, context: RunContext, routine: ProgramRoutine, vars: dict[str, Any] | None = None + ) -> Any: """ This implementation is not yet working. It is a placeholder for the future implementation of running a program routine. A program routine @@ -28,3 +26,9 @@ async def run(self, skill: Skill, routine: ProgramRoutine, vars: dict[str, Any] # TODO: execute the program. return + + async def next(self, context: RunContext, routine: ProgramRoutine, message: str) -> Any: + """ + Run the next step in the current routine. + """ + pass diff --git a/libraries/python/skills/skill-library/skill_library/routine_stack.py b/libraries/python/skills/skill-library/skill_library/routine_stack.py new file mode 100644 index 00000000..d3de4002 --- /dev/null +++ b/libraries/python/skills/skill-library/skill_library/routine_stack.py @@ -0,0 +1,101 @@ +from typing import Any, List +from uuid import uuid4 + +from assistant_drive import Drive, IfDriveFileExistsBehavior +from pydantic import BaseModel, Field + + +class RoutineFrame(BaseModel): + name: str + id: str = Field(default_factory=lambda: str(uuid4())) + state: dict[str, Any] = Field(default_factory=dict) + + +class RoutineStackData(BaseModel): + frames: List[RoutineFrame] + model_config = { + "arbitrary_types_allowed": True, + } + + +STACK_FILENAME = "routine_stack.json" + + +class RoutineStack: + def __init__(self, drive: Drive): + self.drive = drive + + async def get(self) -> List[RoutineFrame]: + try: + stack = self.drive.read_model(RoutineStackData, STACK_FILENAME) + except FileNotFoundError: + stack = RoutineStackData(frames=[]) + await self.set(stack.frames) + return stack.frames + + async def push(self, name: str) -> str: + stack = await self.get() + frame = RoutineFrame(name=name) + stack.append(frame) + await self.set(stack) + return frame.id + + async def pop(self) -> RoutineFrame | None: + stack = await self.get() + if not stack: + return None + frame = stack.pop() + await self.set(stack) + return frame + + async def peek(self) -> RoutineFrame | None: + stack = await self.get() + if not stack: + return None + return stack[-1] + + async def update(self, frame: RoutineFrame) -> None: + """Updates the top frame in the stack.""" + stack = await self.get() + stack[-1] = frame + await self.set(stack) + + async def set(self, frames: List[RoutineFrame]) -> None: + """Replaces the stack with the given list of frames.""" + self.drive.write_model( + RoutineStackData(frames=frames), + STACK_FILENAME, + if_exists=IfDriveFileExistsBehavior.OVERWRITE, + ) + + async def get_current_state(self) -> dict[str, Any]: + """Returns the state of the current routine.""" + frame = await self.peek() + if frame: + return frame.state + return {} + + async def set_current_state(self, state: dict[str, Any]) -> None: + """Updates the state of the current routine.""" + frame = await self.peek() + if frame: + frame.state = state + await self.update(frame) + + async def get_current_state_key(self, key: str) -> Any: + state = await self.get_current_state() + return state.get(key) + + async def set_current_state_key(self, key: str, value: Any) -> None: + state = await self.get_current_state() + state[key] = value + await self.set_current_state(state) + + async def clear(self) -> None: + await self.set([]) + + async def length(self) -> int: + return len(await self.get()) + + async def string(self) -> str: + return str([f"{frame.name}.{frame.id}" for frame in await self.get()]) diff --git a/libraries/python/skills/skill-library/skill_library/run_context.py b/libraries/python/skills/skill-library/skill_library/run_context.py new file mode 100644 index 00000000..4c14fbff --- /dev/null +++ b/libraries/python/skills/skill-library/skill_library/run_context.py @@ -0,0 +1,72 @@ +import logging +from os import PathLike +from typing import Any, Callable, Coroutine, Optional +from uuid import uuid4 + +from assistant_drive import Drive, DriveConfig, IfDriveFileExistsBehavior +from context import ContextProtocol +from events.events import EventProtocol + +from .routine_stack import RoutineStack + + +class LogEmitter: + """A simple event emitter that logs the event to the console. This will be + what is used unless a different emitter is provided.""" + + def emit(self, event: EventProtocol) -> None: + logging.info(event.to_json()) + + +class RunContext(ContextProtocol): + def __init__( + self, + session_id: str, + assistant_drive: Drive, + emit: Callable[[EventProtocol], None], + run_routine: Callable[["RunContext", str, Optional[dict[str, Any]]], Coroutine[Any, Any, Any]], + metadata_drive_root: PathLike | None = None, + ) -> None: + # A session id is useful for maintaining consistent session state across all + # consumers of this context. For example, a session id can be set in an + # assistant and all functions called by that assistant can should receive + # this same context object to know which session is being used. + self.session_id: str = session_id or str(uuid4()) + + # The assistant drive is a drive object that can be used to read and + # write files to a particular location. The assistant drive should be + # used for assistant-specific data and not for general data storage. + self.assistant_drive: Drive = assistant_drive + + # A "run" is a particular series of calls within a session. The initial call will + # set the run id and all subsequent calls will use the same run id. This is useful + # for logging, metrics, and debugging. + self.run_id: str = str(uuid4()) + + # The emit function is used to send events to the event bus. The component + # that creates this context object will be responsible for instantiating an + # event bus and handling the events sent to it with this function. + self.emit = emit or LogEmitter().emit + + # A metadrive to be used for managing assistant metadata. This can be + # useful for storing session data, logs, and other information that + # needs to be persisted across different calls to the assistant. + self.metadrive: Drive = Drive( + DriveConfig( + root=metadata_drive_root or f".data/{session_id}/.assistant", + default_if_exists_behavior=IfDriveFileExistsBehavior.OVERWRITE, + ) + ) + + # Functions for running routines. + self.run_routine = run_routine + + # The routine stack is used to keep track of the current routine being + # run by the assistant. + self.routine_stack: RoutineStack = RoutineStack(self.metadrive) + + # Helper functions for managing state of the current routine being run. + self.state = self.routine_stack.get_current_state + self.state_key = self.routine_stack.get_current_state_key + self.update_state = self.routine_stack.set_current_state + self.update_state_key = self.routine_stack.set_current_state_key diff --git a/libraries/python/skills/skill-library/skill_library/skill.py b/libraries/python/skills/skill-library/skill_library/skill.py index b6de3da1..f9c4e99d 100644 --- a/libraries/python/skills/skill-library/skill_library/skill.py +++ b/libraries/python/skills/skill-library/skill_library/skill.py @@ -1,13 +1,20 @@ +import logging from typing import Any, Callable -from chat_driver import TEXT_RESPONSE_FORMAT, ChatDriver, ChatDriverConfig -from context import Context -from events import BaseEvent -from function_registry import FunctionRegistry +from events import BaseEvent, EventProtocol from openai.types.chat.completion_create_params import ResponseFormat +from openai_client.chat_driver import ChatDriver, ChatDriverConfig +from openai_client.completion import TEXT_RESPONSE_FORMAT +from .actions import Actions from .routine import RoutineTypes +EmitterType = Callable[[EventProtocol], None] + + +def log_emitter(event: EventProtocol) -> None: + logging.info(event) + class Skill: """ @@ -19,16 +26,14 @@ def __init__( self, name: str, description: str, - context: Context, - chat_driver_config: ChatDriverConfig | None = None, skill_actions: list[Callable] = [], # Functions to be registered as skill actions. routines: list[RoutineTypes] = [], + chat_driver_config: ChatDriverConfig | None = None, ) -> None: # A skill should have a short name so that user commands can be routed # to them efficiently. self.name = name self.description = description - self.context = context self.routines: dict[str, RoutineTypes] = {routine.name: routine for routine in routines} # The routines in this skill might use actions from other skills. The dependency on @@ -44,13 +49,17 @@ def __init__( # skill subclass). self.chat_driver = ChatDriver(chat_driver_config) if chat_driver_config else None + # TODO: Configure up one of these separate from the chat driver. + self.openai_client = chat_driver_config.openai_client if chat_driver_config else None + # Register all provided actions with the action registry. - self.action_registry = FunctionRegistry(context, skill_actions) + self.action_registry = Actions() + self.action_registry.add_functions(skill_actions) # Also, register any commands provided by the chat driver. All # commands will be available to the skill. if self.chat_driver: - self.action_registry.register_functions(self.chat_driver.get_commands()) + self.action_registry.add_functions(self.chat_driver.get_commands()) # Make actions available to be called as attributes from the skill # directly. @@ -74,10 +83,10 @@ async def respond( ) def get_actions(self) -> list[Callable]: - return [function.fn for function in self.action_registry.get_functions()] + return [function.fn for function in self.action_registry.get_actions()] def list_actions(self) -> list[str]: - return self.action_registry.list_functions() + return [action.name for action in self.action_registry.get_actions()] def add_routine(self, routine: RoutineTypes) -> None: """ @@ -97,18 +106,6 @@ def list_routines(self) -> list[str]: def has_routine(self, name: str) -> bool: return name in self.routines - # Convenience methods for getting the chat driver's functions and commands. - - def get_chat_functions(self) -> list[Callable]: - if not self.chat_driver: - return [] - return self.chat_driver.get_functions() - - def get_chat_commands(self) -> list[Callable]: - if not self.chat_driver: - return [] - return self.chat_driver.get_commands() - def __str__(self) -> str: return f"{self.name} - {self.description}" diff --git a/libraries/python/skills/skill-library/skill_library/skill_registry.py b/libraries/python/skills/skill-library/skill_library/skill_registry.py index 04d9d948..d788989e 100644 --- a/libraries/python/skills/skill-library/skill_library/skill_registry.py +++ b/libraries/python/skills/skill-library/skill_library/skill_registry.py @@ -1,9 +1,12 @@ -from .routine import RoutineTypes -from .skill import Skill -import os import importlib +import os from typing import Any +from .routine import FunctionRoutine, InstructionRoutine, ProgramRoutine, RoutineTypes +from .routine_runners import FunctionRoutineRunner, InstructionRoutineRunner, ProgramRoutineRunner +from .run_context import RunContext +from .skill import Skill + class SkillRegistry: """ @@ -82,15 +85,75 @@ def has_routine(self, routine_name: str) -> bool: return False return skill.has_routine(routine) - def get_routine(self, routine_name: str) -> tuple[Skill | None, RoutineTypes | None]: + def get_routine(self, routine_name: str) -> RoutineTypes | None: """ Get a routine by name. """ skill_name, routine = routine_name.split(".") skill = self.get_skill(skill_name) if not skill: - return None, None - return skill, skill.get_routine(routine) + return None + return skill.get_routine(routine) + + async def run_routine_by_name(self, context: RunContext, name: str, vars: dict[str, Any] | None = None) -> Any: + """ + Run an assistant routine by name (.). + """ + routine = self.get_routine(name) + if not routine: + raise ValueError(f"Routine {name} not found.") + response = await self.run_routine(context, routine, vars) + return response + + async def run_routine(self, context: RunContext, routine: RoutineTypes, vars: dict[str, Any] | None = None) -> Any: + """ + Run an assistant routine. This is going to be much of the + magic of the assistant. Currently, is just runs through the + steps of a routine, but this will get much more sophisticated. + It will need to handle configuration, managing results of steps, + handling errors and retries, etc. ALso, this is where we will put + meta-cognitive functions such as having the assistant create a plan + from the routine and executing it dynamically while monitoring progress. + name = . + """ + await context.routine_stack.push(routine.fullname()) + match routine: + case InstructionRoutine(): + runner = InstructionRoutineRunner() + done = await runner.run(context, routine, vars) + case ProgramRoutine(): + runner = ProgramRoutineRunner() + done = await runner.run(context, routine, vars) + case FunctionRoutine(): + runner = FunctionRoutineRunner() + done = await runner.run(context, routine, vars) + if done: + _ = await context.routine_stack.pop() + + async def step_active_routine(self, context: RunContext, message: str) -> None: + """Run another step in the current routine.""" + routine_frame = await context.routine_stack.peek() + if not routine_frame: + raise ValueError("No routine to run.") + + routine = self.get_routine(routine_frame.name) + if not routine: + raise ValueError(f"Routine {routine_frame.name} not found.") + + match routine: + case InstructionRoutine(): + runner = InstructionRoutineRunner() + done = await runner.next(context, routine, message) + case ProgramRoutine(): + runner = ProgramRoutineRunner() + done = await runner.next(context, routine, message) + case FunctionRoutine(): + runner = FunctionRoutineRunner() + done = await runner.next(context, routine, message) + + if done: + await context.routine_stack.pop() + # TODO: Manage return state for composition in parent steps. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) diff --git a/libraries/python/skills/skill-library/skill_library/tests/test_routine_stack.py b/libraries/python/skills/skill-library/skill_library/tests/test_routine_stack.py new file mode 100644 index 00000000..9dee2302 --- /dev/null +++ b/libraries/python/skills/skill-library/skill_library/tests/test_routine_stack.py @@ -0,0 +1,56 @@ +from assistant_drive import Drive, DriveConfig, IfDriveFileExistsBehavior +from skill_library.routine_stack import RoutineStack + + +async def test_routine_stack(): + drive = Drive( + DriveConfig( + root=".data/test", default_if_exists_behavior=IfDriveFileExistsBehavior.OVERWRITE + ) + ) + stack = RoutineStack(drive) + + await stack.clear() + + # Test push + frame_id = await stack.push("test") + assert frame_id + + # Get + frames = await stack.get() + assert frames + assert len(frames) == 1 + assert frames[0].id == frame_id + + # Test peek + frame = await stack.peek() + assert frame + assert frame.id == frame_id + assert frame.state == {} + + # Test set_current_state_key + await stack.set_current_state_key("test", "value") + value = await stack.get_current_state_key("test") + assert value == "value" + + # Test get_current_state + state = await stack.get_current_state() + assert state + assert state["test"] == "value" + + # Test set_current_state + new_state = {"new": "state"} + await stack.set_current_state(new_state) + state = await stack.get_current_state() + assert state + assert state == new_state + + # Test pop + frame = await stack.pop() + assert frame + assert frame.id == frame_id + assert await stack.length() == 0 + + # Test pop empty + frame = await stack.pop() + assert not frame diff --git a/libraries/python/skills/skill-library/uv.lock b/libraries/python/skills/skill-library/uv.lock index 845d7696..f6637e77 100644 --- a/libraries/python/skills/skill-library/uv.lock +++ b/libraries/python/skills/skill-library/uv.lock @@ -98,15 +98,15 @@ wheels = [ [[package]] name = "anyio" -version = "4.6.0" +version = "4.6.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 } +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 }, + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, ] [[package]] @@ -122,6 +122,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262 }, ] +[[package]] +name = "assistant-drive" +version = "0.1.0" +source = { editable = "../../assistant-drive" } +dependencies = [ + { name = "context" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../context" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.5.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -146,16 +171,16 @@ wheels = [ [[package]] name = "azure-core" -version = "1.31.0" +version = "1.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/7a/f79ad135a276a37e61168495697c14ba1721a52c3eab4dae2941929c79f8/azure_core-1.31.0.tar.gz", hash = "sha256:656a0dd61e1869b1506b7c6a3b31d62f15984b1a573d6326f6aa2f3e4123284b", size = 277147 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/ee/668328306a9e963a5ad9f152cd98c7adad86c822729fd1d2a01613ad1e67/azure_core-1.32.0.tar.gz", hash = "sha256:22b3c35d6b2dae14990f6c1be2912bf23ffe50b220e708a28ab1bb92b1c730e5", size = 279128 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/8e/fcb6a77d3029d2a7356f38dbc77cf7daa113b81ddab76b5593d23321e44c/azure_core-1.31.0-py3-none-any.whl", hash = "sha256:22954de3777e0250029360ef31d80448ef1be13b80a459bff80ba7073379e2cd", size = 197399 }, + { url = "https://files.pythonhosted.org/packages/39/83/325bf5e02504dbd8b4faa98197a44cdf8a325ef259b48326a2b6f17f8383/azure_core-1.32.0-py3-none-any.whl", hash = "sha256:eac191a0efb23bfa83fddf321b27b122b4ec847befa3091fa736a5c32c50d7b4", size = 198855 }, ] [package.optional-dependencies] @@ -165,7 +190,7 @@ aio = [ [[package]] name = "azure-identity" -version = "1.18.0" +version = "1.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -174,9 +199,9 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/5d/1c7da35dd640b4a95a38f980bb6b0b56c4e91d5b3d518ac11a2c4ebf5f62/azure_identity-1.18.0.tar.gz", hash = "sha256:f567579a65d8932fa913c76eddf3305101a15e5727a5e4aa5df649a0f553d4c3", size = 263322 } +sdist = { url = "https://files.pythonhosted.org/packages/aa/91/cbaeff9eb0b838f0d35b4607ac1c6195c735c8eb17db235f8f60e622934c/azure_identity-1.19.0.tar.gz", hash = "sha256:500144dc18197d7019b81501165d4fa92225f03778f17d7ca8a2a180129a9c83", size = 263058 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/71/1d1bb387b6acaa5daa3e703c70dde3d54823ccd229bd6730de6e724f296e/azure_identity-1.18.0-py3-none-any.whl", hash = "sha256:bccf6106245b49ff41d0c4cd7b72851c5a2ba3a32cef7589da246f5727f26f02", size = 187179 }, + { url = "https://files.pythonhosted.org/packages/f0/d5/3995ed12f941f4a41a273d9b1709282e825ef87ed8eab3833038fee54d59/azure_identity-1.19.0-py3-none-any.whl", hash = "sha256:e3f6558c181692d7509f09de10cca527c7dce426776454fb97df512a46527e81", size = 187587 }, ] [[package]] @@ -244,74 +269,56 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, - { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, - { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, - { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, - { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, - { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, - { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, - { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, - { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, - { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, - { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, - { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, - { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, - { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, - { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, - { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, - { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, - { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, - { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, - { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, - { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, - { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, - { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, - { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, - { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, - { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, - { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, - { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, - { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, - { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, - { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, -] - -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "../../chat-driver" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../context" }, - { name = "events", editable = "../../events" }, - { name = "function-registry", editable = "../../function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../../openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, + { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, + { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, + { url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 }, + { url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 }, + { url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 }, + { url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 }, + { url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 }, + { url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 }, + { url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 }, + { url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 }, + { url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 }, + { url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 }, + { url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 }, + { url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, ] [[package]] @@ -355,31 +362,31 @@ dev = [ [[package]] name = "cryptography" -version = "43.0.1" +version = "43.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222 }, - { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751 }, - { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827 }, - { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034 }, - { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407 }, - { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457 }, - { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499 }, - { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504 }, - { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456 }, - { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263 }, - { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368 }, - { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750 }, - { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925 }, - { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152 }, - { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392 }, - { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 }, - { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 }, - { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 }, +sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303 }, + { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905 }, + { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271 }, + { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606 }, + { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484 }, + { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131 }, + { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647 }, + { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873 }, + { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039 }, + { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984 }, + { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968 }, + { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754 }, + { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458 }, + { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220 }, + { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898 }, + { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592 }, + { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145 }, + { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026 }, ] [[package]] @@ -529,40 +536,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -648,6 +621,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 }, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + [[package]] name = "isodate" version = "0.7.2" @@ -671,34 +653,46 @@ wheels = [ [[package]] name = "jiter" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/1a/aa64be757afc614484b370a4d9fc1747dc9237b37ce464f7f9d9ca2a3d38/jiter-0.5.0.tar.gz", hash = "sha256:1d916ba875bcab5c5f7d927df998c4cb694d27dceddf3392e58beaf10563368a", size = 158300 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/5f/3ac960ed598726aae46edea916e6df4df7ff6fe084bc60774b95cf3154e6/jiter-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4c8e1ed0ef31ad29cae5ea16b9e41529eb50a7fba70600008e9f8de6376d553", size = 284131 }, - { url = "https://files.pythonhosted.org/packages/03/eb/2308fa5f5c14c97c4c7720fef9465f1fa0771826cddb4eec9866bdd88846/jiter-0.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6f16e21276074a12d8421692515b3fd6d2ea9c94fd0734c39a12960a20e85f3", size = 299310 }, - { url = "https://files.pythonhosted.org/packages/3c/f6/dba34ca10b44715fa5302b8e8d2113f72eb00a9297ddf3fa0ae4fd22d1d1/jiter-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5280e68e7740c8c128d3ae5ab63335ce6d1fb6603d3b809637b11713487af9e6", size = 332282 }, - { url = "https://files.pythonhosted.org/packages/69/f7/64e0a7439790ec47f7681adb3871c9d9c45fff771102490bbee5e92c00b7/jiter-0.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:583c57fc30cc1fec360e66323aadd7fc3edeec01289bfafc35d3b9dcb29495e4", size = 342370 }, - { url = "https://files.pythonhosted.org/packages/55/31/1efbfff2ae8e4d919144c53db19b828049ad0622a670be3bbea94a86282c/jiter-0.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26351cc14507bdf466b5f99aba3df3143a59da75799bf64a53a3ad3155ecded9", size = 363591 }, - { url = "https://files.pythonhosted.org/packages/30/c3/7ab2ca2276426a7398c6dfb651e38dbc81954c79a3bfbc36c514d8599499/jiter-0.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829df14d656b3fb87e50ae8b48253a8851c707da9f30d45aacab2aa2ba2d614", size = 378551 }, - { url = "https://files.pythonhosted.org/packages/47/e7/5d88031cd743c62199b125181a591b1671df3ff2f6e102df85c58d8f7d31/jiter-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42a4bdcf7307b86cb863b2fb9bb55029b422d8f86276a50487982d99eed7c6e", size = 319152 }, - { url = "https://files.pythonhosted.org/packages/4c/2d/09ea58e1adca9f0359f3d41ef44a1a18e59518d7c43a21f4ece9e72e28c0/jiter-0.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04d461ad0aebf696f8da13c99bc1b3e06f66ecf6cfd56254cc402f6385231c06", size = 357377 }, - { url = "https://files.pythonhosted.org/packages/7d/2f/83ff1058cb56fc3ff73e0d3c6440703ddc9cdb7f759b00cfbde8228fc435/jiter-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6375923c5f19888c9226582a124b77b622f8fd0018b843c45eeb19d9701c403", size = 511091 }, - { url = "https://files.pythonhosted.org/packages/ae/c9/4f85f97c9894382ab457382337aea0012711baaa17f2ed55c0ff25f3668a/jiter-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cec323a853c24fd0472517113768c92ae0be8f8c384ef4441d3632da8baa646", size = 492948 }, - { url = "https://files.pythonhosted.org/packages/4d/f2/2e987e0eb465e064c5f52c2f29c8d955452e3b316746e326269263bfb1b7/jiter-0.5.0-cp311-none-win32.whl", hash = "sha256:aa1db0967130b5cab63dfe4d6ff547c88b2a394c3410db64744d491df7f069bb", size = 195183 }, - { url = "https://files.pythonhosted.org/packages/ab/59/05d1c3203c349b37c4dd28b02b9b4e5915a7bcbd9319173b4548a67d2e93/jiter-0.5.0-cp311-none-win_amd64.whl", hash = "sha256:aa9d2b85b2ed7dc7697597dcfaac66e63c1b3028652f751c81c65a9f220899ae", size = 191032 }, - { url = "https://files.pythonhosted.org/packages/aa/bd/c3950e2c478161e131bed8cb67c36aed418190e2a961a1c981e69954e54b/jiter-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9f664e7351604f91dcdd557603c57fc0d551bc65cc0a732fdacbf73ad335049a", size = 283511 }, - { url = "https://files.pythonhosted.org/packages/80/1c/8ce58d8c37a589eeaaa5d07d131fd31043886f5e77ab50c00a66d869a361/jiter-0.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:044f2f1148b5248ad2c8c3afb43430dccf676c5a5834d2f5089a4e6c5bbd64df", size = 296974 }, - { url = "https://files.pythonhosted.org/packages/4d/b8/6faeff9eed8952bed93a77ea1cffae7b946795b88eafd1a60e87a67b09e0/jiter-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:702e3520384c88b6e270c55c772d4bd6d7b150608dcc94dea87ceba1b6391248", size = 331897 }, - { url = "https://files.pythonhosted.org/packages/4f/54/1d9a2209b46d39ce6f0cef3ad87c462f9c50312ab84585e6bd5541292b35/jiter-0.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:528d742dcde73fad9d63e8242c036ab4a84389a56e04efd854062b660f559544", size = 342962 }, - { url = "https://files.pythonhosted.org/packages/2a/de/90360be7fc54b2b4c2dfe79eb4ed1f659fce9c96682e6a0be4bbe71371f7/jiter-0.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf80e5fe6ab582c82f0c3331df27a7e1565e2dcf06265afd5173d809cdbf9ba", size = 363844 }, - { url = "https://files.pythonhosted.org/packages/ba/ad/ef32b173191b7a53ea8a6757b80723cba321f8469834825e8c71c96bde17/jiter-0.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44dfc9ddfb9b51a5626568ef4e55ada462b7328996294fe4d36de02fce42721f", size = 378709 }, - { url = "https://files.pythonhosted.org/packages/07/de/353ce53743c0defbbbd652e89c106a97dbbac4eb42c95920b74b5056b93a/jiter-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c451f7922992751a936b96c5f5b9bb9312243d9b754c34b33d0cb72c84669f4e", size = 319038 }, - { url = "https://files.pythonhosted.org/packages/3f/92/42d47310bf9530b9dece9e2d7c6d51cf419af5586ededaf5e66622d160e2/jiter-0.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:308fce789a2f093dca1ff91ac391f11a9f99c35369117ad5a5c6c4903e1b3e3a", size = 357763 }, - { url = "https://files.pythonhosted.org/packages/bd/8c/2bb76a9a84474d48fdd133d3445db8a4413da4e87c23879d917e000a9d87/jiter-0.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7f5ad4a7c6b0d90776fdefa294f662e8a86871e601309643de30bf94bb93a64e", size = 511031 }, - { url = "https://files.pythonhosted.org/packages/33/4f/9f23d79c0795e0a8e56e7988e8785c2dcda27e0ed37977256d50c77c6a19/jiter-0.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea189db75f8eca08807d02ae27929e890c7d47599ce3d0a6a5d41f2419ecf338", size = 493042 }, - { url = "https://files.pythonhosted.org/packages/df/67/8a4f975aa834b8aecdb6b131422390173928fd47f42f269dcc32034ab432/jiter-0.5.0-cp312-none-win32.whl", hash = "sha256:e3bbe3910c724b877846186c25fe3c802e105a2c1fc2b57d6688b9f8772026e4", size = 195405 }, - { url = "https://files.pythonhosted.org/packages/15/81/296b1e25c43db67848728cdab34ac3eb5c5cbb4955ceb3f51ae60d4a5e3d/jiter-0.5.0-cp312-none-win_amd64.whl", hash = "sha256:a586832f70c3f1481732919215f36d41c59ca080fa27a65cf23d9490e75b2ef5", size = 189720 }, +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/3d/4ca1c6b8d1d15ea747da474891f9879c0f0777e2e44e87c0be81657ed016/jiter-0.7.0.tar.gz", hash = "sha256:c061d9738535497b5509f8970584f20de1e900806b239a39a9994fc191dad630", size = 162154 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/01/ac41fe6d402da0ff454e2abaee6b8cc29ad2c97cf985f503e46ca7724aca/jiter-0.7.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:91cec0ad755bd786c9f769ce8d843af955df6a8e56b17658771b2d5cb34a3ff8", size = 292667 }, + { url = "https://files.pythonhosted.org/packages/9d/cb/d2e612729676cbe022ad732aaed9c842ac459a70808a927f7f845cfc6dc1/jiter-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:feba70a28a27d962e353e978dbb6afd798e711c04cb0b4c5e77e9d3779033a1a", size = 306403 }, + { url = "https://files.pythonhosted.org/packages/e9/db/d88002c550f6405dbf98962cc3dc1c8e66de9c4f3246abebe1582b2f919f/jiter-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d866ec066c3616cacb8535dbda38bb1d470b17b25f0317c4540182bc886ce2", size = 329020 }, + { url = "https://files.pythonhosted.org/packages/18/42/54d6527bcdea2909396849491b96a6fe595bd97ec43bdc522399c534ed33/jiter-0.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e7a7a00b6f9f18289dd563596f97ecaba6c777501a8ba04bf98e03087bcbc60", size = 347638 }, + { url = "https://files.pythonhosted.org/packages/ec/12/a3c43061d5e189def91c07472e64a569196687f60c2f86150e29a5692ce7/jiter-0.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aaf564094c7db8687f2660605e099f3d3e6ea5e7135498486674fcb78e29165", size = 373916 }, + { url = "https://files.pythonhosted.org/packages/32/08/2c7432ed26d194927ec07c1dfc08cdae5b6d302369df7fdda320d6393736/jiter-0.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4d27e09825c1b3c7a667adb500ce8b840e8fc9f630da8454b44cdd4fb0081bb", size = 390942 }, + { url = "https://files.pythonhosted.org/packages/65/9b/70f3ecbd3f18ef19e50fbe5a51bdb1c520282720896c16ae1a68b90675b8/jiter-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca7c287da9c1d56dda88da1d08855a787dbb09a7e2bd13c66a2e288700bd7c7", size = 327315 }, + { url = "https://files.pythonhosted.org/packages/2c/30/ba97e50e5fe1f58a1012257e0cfac0cc09e548b63f81982546c6dac8f1e7/jiter-0.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db19a6d160f093cbc8cd5ea2abad420b686f6c0e5fb4f7b41941ebc6a4f83cda", size = 367159 }, + { url = "https://files.pythonhosted.org/packages/a1/6d/73bb48ca87951c6c01b902258d0b792ed9404980bafceb1aa87ac43eb925/jiter-0.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e46a63c7f877cf7441ffc821c28287cfb9f533ae6ed707bde15e7d4dfafa7ae", size = 514891 }, + { url = "https://files.pythonhosted.org/packages/6f/03/50c665a3d9067c5e384604310d67a184ae3176f27f677ca0c2670bb061ac/jiter-0.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ba426fa7ff21cb119fa544b75dd3fbee6a70e55a5829709c0338d07ccd30e6d", size = 498039 }, + { url = "https://files.pythonhosted.org/packages/85/35/9fb7c7fea9b9c159d2089ea9b5ff4e3e56e2d42069456e3568dadf904e99/jiter-0.7.0-cp311-none-win32.whl", hash = "sha256:c07f55a64912b0c7982377831210836d2ea92b7bd343fca67a32212dd72e38e0", size = 198533 }, + { url = "https://files.pythonhosted.org/packages/96/19/e9b32c69c6dea404d983847e92cf86c3287b0f2f3e7621180a544c0ff153/jiter-0.7.0-cp311-none-win_amd64.whl", hash = "sha256:ed27b2c43e1b5f6c7fedc5c11d4d8bfa627de42d1143d87e39e2e83ddefd861a", size = 205728 }, + { url = "https://files.pythonhosted.org/packages/d9/7b/ed881a65e8f0989913408643b68a3a0913365c5c3884e85bae01a9679dd5/jiter-0.7.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac7930bcaaeb1e229e35c91c04ed2e9f39025b86ee9fc3141706bbf6fff4aeeb", size = 292762 }, + { url = "https://files.pythonhosted.org/packages/d8/44/d0409912bc28508abffd99b9d55baba869592c1d27f9ee1cc035ef62371e/jiter-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:571feae3e7c901a8eedde9fd2865b0dfc1432fb15cab8c675a8444f7d11b7c5d", size = 302790 }, + { url = "https://files.pythonhosted.org/packages/8d/4f/38d0e87c8863c1b1f2dbac48acca8da85f6931a7b735e7163781843170a5/jiter-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8af4df8a262fa2778b68c2a03b6e9d1cb4d43d02bea6976d46be77a3a331af1", size = 329246 }, + { url = "https://files.pythonhosted.org/packages/88/3c/1af75094cbeba25df672b3f772dc717203be843e08248a0e03ef0ca382bc/jiter-0.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd028d4165097a611eb0c7494d8c1f2aebd46f73ca3200f02a175a9c9a6f22f5", size = 347808 }, + { url = "https://files.pythonhosted.org/packages/0b/74/55f00ca01223665e1418bec76cdeebb17a5f9ffae94e886da5c9bef5abd2/jiter-0.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b487247c7836810091e9455efe56a52ec51bfa3a222237e1587d04d3e04527", size = 374011 }, + { url = "https://files.pythonhosted.org/packages/f7/ae/c1c892861796aa0adb720da75c59de5dbcf74ad51243c2aeea46681dcb16/jiter-0.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6d28a92f28814e1a9f2824dc11f4e17e1df1f44dc4fdeb94c5450d34bcb2602", size = 388863 }, + { url = "https://files.pythonhosted.org/packages/9d/a2/914587a68cba16920b1f979267a4e5c19f6977cac8fb8a6fbbd00035d0ed/jiter-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90443994bbafe134f0b34201dad3ebe1c769f0599004084e046fb249ad912425", size = 326765 }, + { url = "https://files.pythonhosted.org/packages/c8/ea/79abc48a6c9ba9ee3ccb0c194ec4cc1dd62e523c7c7003189380d00e5f16/jiter-0.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f9abf464f9faac652542ce8360cea8e68fba2b78350e8a170248f9bcc228702a", size = 367756 }, + { url = "https://files.pythonhosted.org/packages/4b/eb/ddc874819382081f9ad71cf681ee76450b17ac981f78a8db6408e7e28f34/jiter-0.7.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db7a8d99fc5f842f7d2852f06ccaed066532292c41723e5dff670c339b649f88", size = 515349 }, + { url = "https://files.pythonhosted.org/packages/af/9d/816d2d7f19070b72cf0133437cbacf99a9202f6fbbc2cfa2111fb686b0e0/jiter-0.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:15cf691ebd8693b70c94627d6b748f01e6d697d9a6e9f2bc310934fcfb7cf25e", size = 498050 }, + { url = "https://files.pythonhosted.org/packages/3e/a5/d0afb758c02d2d3c8ac3214a5be26579594d790944eaee7a47af06915e0e/jiter-0.7.0-cp312-none-win32.whl", hash = "sha256:9dcd54fa422fb66ca398bec296fed5f58e756aa0589496011cfea2abb5be38a5", size = 198912 }, + { url = "https://files.pythonhosted.org/packages/d0/8e/80b2afd0391a3530966d8fc2f9c104955ba41093b3c319ae40b25e68e323/jiter-0.7.0-cp312-none-win_amd64.whl", hash = "sha256:cc989951f73f9375b8eacd571baaa057f3d7d11b7ce6f67b9d54642e7475bfad", size = 199942 }, + { url = "https://files.pythonhosted.org/packages/50/bb/82c7180dc126687ddcc25386727b3a1688ab8eff496afe7838b69886fcc7/jiter-0.7.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:24cecd18df540963cd27c08ca5ce1d0179f229ff78066d9eecbe5add29361340", size = 292624 }, + { url = "https://files.pythonhosted.org/packages/11/c2/3b6d4596eab2ff81ebfe5bab779f457433cc2ffb8a2d1d6ab5ac187f26f6/jiter-0.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d41b46236b90b043cca73785674c23d2a67d16f226394079d0953f94e765ed76", size = 304723 }, + { url = "https://files.pythonhosted.org/packages/49/65/56f78dfccfb22e43815cad4a468b4360f8cfebecc024edb5e2a625b83a04/jiter-0.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b160db0987171365c153e406a45dcab0ee613ae3508a77bfff42515cb4ce4d6e", size = 328319 }, + { url = "https://files.pythonhosted.org/packages/fd/f2/9e3ed9ac0b122dd65250fc83cd0f0979da82f055ef6041411191f6301284/jiter-0.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1c8d91e0f0bd78602eaa081332e8ee4f512c000716f5bc54e9a037306d693a7", size = 347323 }, + { url = "https://files.pythonhosted.org/packages/42/18/24517f9f8575daf36fdac9dd53fcecde3d4c5bdd9f7b97a55e26ed2555b5/jiter-0.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997706c683195eeff192d2e5285ce64d2a610414f37da3a3f2625dcf8517cf90", size = 374073 }, + { url = "https://files.pythonhosted.org/packages/a1/b1/b368ccdeff3eabb4b293a21a94317a6f717ecc5bfbfca4eecd12ff39da3f/jiter-0.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ea52a8a0ff0229ab2920284079becd2bae0688d432fca94857ece83bb49c541", size = 388224 }, + { url = "https://files.pythonhosted.org/packages/92/1e/cc3d0655bcbc026e4b7746cb1ccab10d6eb2c29ffa64e574072db4d55f73/jiter-0.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d77449d2738cf74752bb35d75ee431af457e741124d1db5e112890023572c7c", size = 326145 }, + { url = "https://files.pythonhosted.org/packages/bb/24/d410c732326738d4f392689621ff14e10d3717efe7de9ecb97c44d8765a3/jiter-0.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8203519907a1d81d6cb00902c98e27c2d0bf25ce0323c50ca594d30f5f1fbcf", size = 366857 }, + { url = "https://files.pythonhosted.org/packages/14/a1/53df95b8248968936e7ba9eb5839918e3cfd183e56356d2961b9b29a49fc/jiter-0.7.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41d15ccc53931c822dd7f1aebf09faa3cda2d7b48a76ef304c7dbc19d1302e51", size = 514972 }, + { url = "https://files.pythonhosted.org/packages/97/c8/1876add533606ff1204450dd2564638cac7f164ff90844cb921cdf25cf68/jiter-0.7.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:febf3179b2fabf71fbd2fd52acb8594163bb173348b388649567a548f356dbf6", size = 497728 }, + { url = "https://files.pythonhosted.org/packages/94/31/1e59f246e264414b004864b63783e54aa3397be88f53dda3b01db3ae4251/jiter-0.7.0-cp313-none-win32.whl", hash = "sha256:4a8e2d866e7eda19f012444e01b55079d8e1c4c30346aaac4b97e80c54e2d6d3", size = 198660 }, + { url = "https://files.pythonhosted.org/packages/ca/96/58b3d260e212add0087563672931b1176e70bef1225839a4470ec66157a5/jiter-0.7.0-cp313-none-win_amd64.whl", hash = "sha256:7417c2b928062c496f381fb0cb50412eee5ad1d8b53dbc0e011ce45bb2de522c", size = 199305 }, ] [[package]] @@ -853,7 +847,7 @@ wheels = [ [[package]] name = "openai" -version = "1.51.0" +version = "1.54.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -865,9 +859,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/af/cc59b1447f5a02bb1f25b9b0cd94b607aa2c969a81d9a244d4067f91f6fe/openai-1.51.0.tar.gz", hash = "sha256:8dc4f9d75ccdd5466fc8c99a952186eddceb9fd6ba694044773f3736a847149d", size = 306880 } +sdist = { url = "https://files.pythonhosted.org/packages/da/26/f1a79d8332ac5ed38fdc347701aa4a7ad8f8f66ec3f9880122c455d7ffb1/openai-1.54.2.tar.gz", hash = "sha256:0dbb8f2bb36f7ff1d200d103b9f95c35773ed3248e3a697b02f5a39015627e5e", size = 312551 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/08/9f22356d4fbd273f734db1e6663b7ca6987943080567f5580471022e57ca/openai-1.51.0-py3-none-any.whl", hash = "sha256:d9affafb7e51e5a27dce78589d4964ce4d6f6d560307265933a94b2e3f3c5d2c", size = 383533 }, + { url = "https://files.pythonhosted.org/packages/f6/0f/ea8717dedbef16fa61a0bdeb0b7ae96ff574933ed5932102d3f5a4ce01a1/openai-1.54.2-py3-none-any.whl", hash = "sha256:77010b439e69d37f67cc2f44eaa62b2b6d5a60add2d8636e4603c0e762982708", size = 389315 }, ] [[package]] @@ -878,7 +872,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -891,7 +885,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../function-registry" }, + { name = "events", editable = "../../events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, @@ -960,6 +954,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 }, ] +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + [[package]] name = "portalocker" version = "2.10.1" @@ -1101,15 +1104,15 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.5.2" +version = "2.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/27/0bed9dd26b93328b60a1402febc780e7be72b42847fa8b5c94b7d0aeb6d1/pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0", size = 70938 } +sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646 } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/8d/29e82e333f32d9e2051c10764b906c2a6cd140992910b5f49762790911ba/pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907", size = 26864 }, + { url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595 }, ] [[package]] @@ -1135,6 +1138,45 @@ crypto = [ { name = "cryptography" }, ] +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, +] + +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 }, +] + +[[package]] +name = "pytest-repeat" +version = "0.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/5e/99365eb229efff0b1bd475886150fc6db9937ab7e1bd21f6f65c1279e0eb/pytest_repeat-0.9.3.tar.gz", hash = "sha256:ffd3836dfcd67bb270bec648b330e20be37d2966448c4148c4092d1e8aba8185", size = 6272 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/a8/0a0aec0c2541b8baf4a0b95af2ba99abce217ee43534adf9cb7c908cf184/pytest_repeat-0.9.3-py3-none-any.whl", hash = "sha256:26ab2df18226af9d5ce441c858f273121e92ff55f5bb311d25755b8d7abdd8ed", size = 4196 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1190,15 +1232,18 @@ wheels = [ [[package]] name = "pywin32" -version = "306" +version = "308" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 }, - { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 }, - { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 }, - { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 }, - { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 }, - { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 }, + { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, + { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, + { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, ] [[package]] @@ -1238,55 +1283,55 @@ wheels = [ [[package]] name = "regex" -version = "2024.9.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/38/148df33b4dbca3bd069b963acab5e0fa1a9dbd6820f8c322d0dd6faeff96/regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd", size = 399403 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/a1/d526b7b6095a0019aa360948c143aacfeb029919c898701ce7763bbe4c15/regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df", size = 482483 }, - { url = "https://files.pythonhosted.org/packages/32/d9/bfdd153179867c275719e381e1e8e84a97bd186740456a0dcb3e7125c205/regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268", size = 287442 }, - { url = "https://files.pythonhosted.org/packages/33/c4/60f3370735135e3a8d673ddcdb2507a8560d0e759e1398d366e43d000253/regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad", size = 284561 }, - { url = "https://files.pythonhosted.org/packages/b1/51/91a5ebdff17f9ec4973cb0aa9d37635efec1c6868654bbc25d1543aca4ec/regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679", size = 791779 }, - { url = "https://files.pythonhosted.org/packages/07/4a/022c5e6f0891a90cd7eb3d664d6c58ce2aba48bff107b00013f3d6167069/regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4", size = 832605 }, - { url = "https://files.pythonhosted.org/packages/ac/1c/3793990c8c83ca04e018151ddda83b83ecc41d89964f0f17749f027fc44d/regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664", size = 818556 }, - { url = "https://files.pythonhosted.org/packages/e9/5c/8b385afbfacb853730682c57be56225f9fe275c5bf02ac1fc88edbff316d/regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50", size = 792808 }, - { url = "https://files.pythonhosted.org/packages/9b/8b/a4723a838b53c771e9240951adde6af58c829fb6a6a28f554e8131f53839/regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199", size = 781115 }, - { url = "https://files.pythonhosted.org/packages/83/5f/031a04b6017033d65b261259c09043c06f4ef2d4eac841d0649d76d69541/regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4", size = 778155 }, - { url = "https://files.pythonhosted.org/packages/fd/cd/4660756070b03ce4a66663a43f6c6e7ebc2266cc6b4c586c167917185eb4/regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd", size = 784614 }, - { url = "https://files.pythonhosted.org/packages/93/8d/65b9bea7df120a7be8337c415b6d256ba786cbc9107cebba3bf8ff09da99/regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f", size = 853744 }, - { url = "https://files.pythonhosted.org/packages/96/a7/fba1eae75eb53a704475baf11bd44b3e6ccb95b316955027eb7748f24ef8/regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96", size = 855890 }, - { url = "https://files.pythonhosted.org/packages/45/14/d864b2db80a1a3358534392373e8a281d95b28c29c87d8548aed58813910/regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1", size = 781887 }, - { url = "https://files.pythonhosted.org/packages/4d/a9/bfb29b3de3eb11dc9b412603437023b8e6c02fb4e11311863d9bf62c403a/regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9", size = 261644 }, - { url = "https://files.pythonhosted.org/packages/c7/ab/1ad2511cf6a208fde57fafe49829cab8ca018128ab0d0b48973d8218634a/regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf", size = 274033 }, - { url = "https://files.pythonhosted.org/packages/6e/92/407531450762bed778eedbde04407f68cbd75d13cee96c6f8d6903d9c6c1/regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7", size = 483590 }, - { url = "https://files.pythonhosted.org/packages/8e/a2/048acbc5ae1f615adc6cba36cc45734e679b5f1e4e58c3c77f0ed611d4e2/regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231", size = 288175 }, - { url = "https://files.pythonhosted.org/packages/8a/ea/909d8620329ab710dfaf7b4adee41242ab7c9b95ea8d838e9bfe76244259/regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d", size = 284749 }, - { url = "https://files.pythonhosted.org/packages/ca/fa/521eb683b916389b4975337873e66954e0f6d8f91bd5774164a57b503185/regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64", size = 795181 }, - { url = "https://files.pythonhosted.org/packages/28/db/63047feddc3280cc242f9c74f7aeddc6ee662b1835f00046f57d5630c827/regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42", size = 835842 }, - { url = "https://files.pythonhosted.org/packages/e3/94/86adc259ff8ec26edf35fcca7e334566c1805c7493b192cb09679f9c3dee/regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766", size = 823533 }, - { url = "https://files.pythonhosted.org/packages/29/52/84662b6636061277cb857f658518aa7db6672bc6d1a3f503ccd5aefc581e/regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a", size = 797037 }, - { url = "https://files.pythonhosted.org/packages/c3/2a/cd4675dd987e4a7505f0364a958bc41f3b84942de9efaad0ef9a2646681c/regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9", size = 784106 }, - { url = "https://files.pythonhosted.org/packages/6f/75/3ea7ec29de0bbf42f21f812f48781d41e627d57a634f3f23947c9a46e303/regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d", size = 782468 }, - { url = "https://files.pythonhosted.org/packages/d3/67/15519d69b52c252b270e679cb578e22e0c02b8dd4e361f2b04efcc7f2335/regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822", size = 790324 }, - { url = "https://files.pythonhosted.org/packages/9c/71/eff77d3fe7ba08ab0672920059ec30d63fa7e41aa0fb61c562726e9bd721/regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0", size = 860214 }, - { url = "https://files.pythonhosted.org/packages/81/11/e1bdf84a72372e56f1ea4b833dd583b822a23138a616ace7ab57a0e11556/regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a", size = 859420 }, - { url = "https://files.pythonhosted.org/packages/ea/75/9753e9dcebfa7c3645563ef5c8a58f3a47e799c872165f37c55737dadd3e/regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a", size = 787333 }, - { url = "https://files.pythonhosted.org/packages/bc/4e/ba1cbca93141f7416624b3ae63573e785d4bc1834c8be44a8f0747919eca/regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776", size = 262058 }, - { url = "https://files.pythonhosted.org/packages/6e/16/efc5f194778bf43e5888209e5cec4b258005d37c613b67ae137df3b89c53/regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009", size = 273526 }, - { url = "https://files.pythonhosted.org/packages/93/0a/d1c6b9af1ff1e36832fe38d74d5c5bab913f2bdcbbd6bc0e7f3ce8b2f577/regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784", size = 483376 }, - { url = "https://files.pythonhosted.org/packages/a4/42/5910a050c105d7f750a72dcb49c30220c3ae4e2654e54aaaa0e9bc0584cb/regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36", size = 288112 }, - { url = "https://files.pythonhosted.org/packages/8d/56/0c262aff0e9224fa7ffce47b5458d373f4d3e3ff84e99b5ff0cb15e0b5b2/regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92", size = 284608 }, - { url = "https://files.pythonhosted.org/packages/b9/54/9fe8f9aec5007bbbbce28ba3d2e3eaca425f95387b7d1e84f0d137d25237/regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86", size = 795337 }, - { url = "https://files.pythonhosted.org/packages/b2/e7/6b2f642c3cded271c4f16cc4daa7231be544d30fe2b168e0223724b49a61/regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85", size = 835848 }, - { url = "https://files.pythonhosted.org/packages/cd/9e/187363bdf5d8c0e4662117b92aa32bf52f8f09620ae93abc7537d96d3311/regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963", size = 823503 }, - { url = "https://files.pythonhosted.org/packages/f8/10/601303b8ee93589f879664b0cfd3127949ff32b17f9b6c490fb201106c4d/regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6", size = 797049 }, - { url = "https://files.pythonhosted.org/packages/ef/1c/ea200f61ce9f341763f2717ab4daebe4422d83e9fd4ac5e33435fd3a148d/regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802", size = 784144 }, - { url = "https://files.pythonhosted.org/packages/d8/5c/d2429be49ef3292def7688401d3deb11702c13dcaecdc71d2b407421275b/regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29", size = 782483 }, - { url = "https://files.pythonhosted.org/packages/12/d9/cbc30f2ff7164f3b26a7760f87c54bf8b2faed286f60efd80350a51c5b99/regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8", size = 790320 }, - { url = "https://files.pythonhosted.org/packages/19/1d/43ed03a236313639da5a45e61bc553c8d41e925bcf29b0f8ecff0c2c3f25/regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84", size = 860435 }, - { url = "https://files.pythonhosted.org/packages/34/4f/5d04da61c7c56e785058a46349f7285ae3ebc0726c6ea7c5c70600a52233/regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554", size = 859571 }, - { url = "https://files.pythonhosted.org/packages/12/7f/8398c8155a3c70703a8e91c29532558186558e1aea44144b382faa2a6f7a/regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8", size = 787398 }, - { url = "https://files.pythonhosted.org/packages/58/3a/f5903977647a9a7e46d5535e9e96c194304aeeca7501240509bde2f9e17f/regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8", size = 262035 }, - { url = "https://files.pythonhosted.org/packages/ff/80/51ba3a4b7482f6011095b3a036e07374f64de180b7d870b704ed22509002/regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f", size = 273510 }, +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, ] [[package]] @@ -1390,11 +1435,11 @@ name = "skill-library" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "chat-driver" }, + { name = "assistant-drive" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "openai" }, + { name = "openai-client" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -1402,13 +1447,20 @@ dependencies = [ { name = "tiktoken" }, ] +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-repeat" }, +] + [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../chat-driver" }, + { name = "assistant-drive", editable = "../../assistant-drive" }, { name = "context", editable = "../../context" }, { name = "events", editable = "../../events" }, - { name = "function-registry", editable = "../../function-registry" }, { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../openai-client" }, { name = "pydantic", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.3.4" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1416,6 +1468,13 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1439,40 +1498,44 @@ wheels = [ [[package]] name = "tiktoken" -version = "0.7.0" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/4a/abaec53e93e3ef37224a4dd9e2fc6bb871e7a538c2b6b9d2a6397271daf4/tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6", size = 33437 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/eb/57492b2568eea1d546da5cc1ae7559d924275280db80ba07e6f9b89a914b/tiktoken-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:10c7674f81e6e350fcbed7c09a65bca9356eaab27fb2dac65a1e440f2bcfe30f", size = 961468 }, - { url = "https://files.pythonhosted.org/packages/30/ef/e07dbfcb2f85c84abaa1b035a9279575a8da0236305491dc22ae099327f7/tiktoken-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:084cec29713bc9d4189a937f8a35dbdfa785bd1235a34c1124fe2323821ee93f", size = 907005 }, - { url = "https://files.pythonhosted.org/packages/ea/9b/f36db825b1e9904c3a2646439cb9923fc1e09208e2e071c6d9dd64ead131/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:811229fde1652fedcca7c6dfe76724d0908775b353556d8a71ed74d866f73f7b", size = 1049183 }, - { url = "https://files.pythonhosted.org/packages/61/b4/b80d1fe33015e782074e96bbbf4108ccd283b8deea86fb43c15d18b7c351/tiktoken-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86b6e7dc2e7ad1b3757e8a24597415bafcfb454cebf9a33a01f2e6ba2e663992", size = 1080830 }, - { url = "https://files.pythonhosted.org/packages/2a/40/c66ff3a21af6d62a7e0ff428d12002c4e0389f776d3ff96dcaa0bb354eee/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1063c5748be36344c7e18c7913c53e2cca116764c2080177e57d62c7ad4576d1", size = 1092967 }, - { url = "https://files.pythonhosted.org/packages/2e/80/f4c9e255ff236e6a69ce44b927629cefc1b63d3a00e2d1c9ed540c9492d2/tiktoken-0.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20295d21419bfcca092644f7e2f2138ff947a6eb8cfc732c09cc7d76988d4a89", size = 1142682 }, - { url = "https://files.pythonhosted.org/packages/b1/10/c04b4ff592a5f46b28ebf4c2353f735c02ae7f0ce1b165d00748ced6467e/tiktoken-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:959d993749b083acc57a317cbc643fb85c014d055b2119b739487288f4e5d1cb", size = 799009 }, - { url = "https://files.pythonhosted.org/packages/1d/46/4cdda4186ce900608f522da34acf442363346688c71b938a90a52d7b84cc/tiktoken-0.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:71c55d066388c55a9c00f61d2c456a6086673ab7dec22dd739c23f77195b1908", size = 960446 }, - { url = "https://files.pythonhosted.org/packages/b6/30/09ced367d280072d7a3e21f34263dfbbf6378661e7a0f6414e7c18971083/tiktoken-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09ed925bccaa8043e34c519fbb2f99110bd07c6fd67714793c21ac298e449410", size = 906652 }, - { url = "https://files.pythonhosted.org/packages/e6/7b/c949e4954441a879a67626963dff69096e3c774758b9f2bb0853f7b4e1e7/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c6c40ff1db0f48a7b4d2dafeae73a5607aacb472fa11f125e7baf9dce73704", size = 1047904 }, - { url = "https://files.pythonhosted.org/packages/50/81/1842a22f15586072280364c2ab1e40835adaf64e42fe80e52aff921ee021/tiktoken-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20b5c6af30e621b4aca094ee61777a44118f52d886dbe4f02b70dfe05c15350", size = 1079836 }, - { url = "https://files.pythonhosted.org/packages/6d/87/51a133a3d5307cf7ae3754249b0faaa91d3414b85c3d36f80b54d6817aa6/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d427614c3e074004efa2f2411e16c826f9df427d3c70a54725cae860f09e4bf4", size = 1092472 }, - { url = "https://files.pythonhosted.org/packages/a5/1f/c93517dc6d3b2c9e988b8e24f87a8b2d4a4ab28920a3a3f3ea338397ae0c/tiktoken-0.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c46d7af7b8c6987fac9b9f61041b452afe92eb087d29c9ce54951280f899a97", size = 1141881 }, - { url = "https://files.pythonhosted.org/packages/bf/4b/48ca098cb580c099b5058bf62c4cb5e90ca6130fa43ef4df27088536245b/tiktoken-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0bc603c30b9e371e7c4c7935aba02af5994a909fc3c0fe66e7004070858d3f8f", size = 799281 }, +sdist = { url = "https://files.pythonhosted.org/packages/37/02/576ff3a6639e755c4f70997b2d315f56d6d71e0d046f4fb64cb81a3fb099/tiktoken-0.8.0.tar.gz", hash = "sha256:9ccbb2740f24542534369c5635cfd9b2b3c2490754a78ac8831d99f89f94eeb2", size = 35107 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/1e/ca48e7bfeeccaf76f3a501bd84db1fa28b3c22c9d1a1f41af9fb7579c5f6/tiktoken-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d622d8011e6d6f239297efa42a2657043aaed06c4f68833550cac9e9bc723ef1", size = 1039700 }, + { url = "https://files.pythonhosted.org/packages/8c/f8/f0101d98d661b34534769c3818f5af631e59c36ac6d07268fbfc89e539ce/tiktoken-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2efaf6199717b4485031b4d6edb94075e4d79177a172f38dd934d911b588d54a", size = 982413 }, + { url = "https://files.pythonhosted.org/packages/ac/3c/2b95391d9bd520a73830469f80a96e3790e6c0a5ac2444f80f20b4b31051/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5637e425ce1fc49cf716d88df3092048359a4b3bbb7da762840426e937ada06d", size = 1144242 }, + { url = "https://files.pythonhosted.org/packages/01/c4/c4a4360de845217b6aa9709c15773484b50479f36bb50419c443204e5de9/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb0e352d1dbe15aba082883058b3cce9e48d33101bdaac1eccf66424feb5b47", size = 1176588 }, + { url = "https://files.pythonhosted.org/packages/f8/a3/ef984e976822cd6c2227c854f74d2e60cf4cd6fbfca46251199914746f78/tiktoken-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56edfefe896c8f10aba372ab5706b9e3558e78db39dd497c940b47bf228bc419", size = 1237261 }, + { url = "https://files.pythonhosted.org/packages/1e/86/eea2309dc258fb86c7d9b10db536434fc16420feaa3b6113df18b23db7c2/tiktoken-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:326624128590def898775b722ccc327e90b073714227175ea8febbc920ac0a99", size = 884537 }, + { url = "https://files.pythonhosted.org/packages/c1/22/34b2e136a6f4af186b6640cbfd6f93400783c9ef6cd550d9eab80628d9de/tiktoken-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:881839cfeae051b3628d9823b2e56b5cc93a9e2efb435f4cf15f17dc45f21586", size = 1039357 }, + { url = "https://files.pythonhosted.org/packages/04/d2/c793cf49c20f5855fd6ce05d080c0537d7418f22c58e71f392d5e8c8dbf7/tiktoken-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe9399bdc3f29d428f16a2f86c3c8ec20be3eac5f53693ce4980371c3245729b", size = 982616 }, + { url = "https://files.pythonhosted.org/packages/b3/a1/79846e5ef911cd5d75c844de3fa496a10c91b4b5f550aad695c5df153d72/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a58deb7075d5b69237a3ff4bb51a726670419db6ea62bdcd8bd80c78497d7ab", size = 1144011 }, + { url = "https://files.pythonhosted.org/packages/26/32/e0e3a859136e95c85a572e4806dc58bf1ddf651108ae8b97d5f3ebe1a244/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2908c0d043a7d03ebd80347266b0e58440bdef5564f84f4d29fb235b5df3b04", size = 1175432 }, + { url = "https://files.pythonhosted.org/packages/c7/89/926b66e9025b97e9fbabeaa59048a736fe3c3e4530a204109571104f921c/tiktoken-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:294440d21a2a51e12d4238e68a5972095534fe9878be57d905c476017bff99fc", size = 1236576 }, + { url = "https://files.pythonhosted.org/packages/45/e2/39d4aa02a52bba73b2cd21ba4533c84425ff8786cc63c511d68c8897376e/tiktoken-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d8f3192733ac4d77977432947d563d7e1b310b96497acd3c196c9bddb36ed9db", size = 883824 }, + { url = "https://files.pythonhosted.org/packages/e3/38/802e79ba0ee5fcbf240cd624143f57744e5d411d2e9d9ad2db70d8395986/tiktoken-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:02be1666096aff7da6cbd7cdaa8e7917bfed3467cd64b38b1f112e96d3b06a24", size = 1039648 }, + { url = "https://files.pythonhosted.org/packages/b1/da/24cdbfc302c98663fbea66f5866f7fa1048405c7564ab88483aea97c3b1a/tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94ff53c5c74b535b2cbf431d907fc13c678bbd009ee633a2aca269a04389f9a", size = 982763 }, + { url = "https://files.pythonhosted.org/packages/e4/f0/0ecf79a279dfa41fc97d00adccf976ecc2556d3c08ef3e25e45eb31f665b/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b231f5e8982c245ee3065cd84a4712d64692348bc609d84467c57b4b72dcbc5", size = 1144417 }, + { url = "https://files.pythonhosted.org/packages/ab/d3/155d2d4514f3471a25dc1d6d20549ef254e2aa9bb5b1060809b1d3b03d3a/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4177faa809bd55f699e88c96d9bb4635d22e3f59d635ba6fd9ffedf7150b9953", size = 1175108 }, + { url = "https://files.pythonhosted.org/packages/19/eb/5989e16821ee8300ef8ee13c16effc20dfc26c777d05fbb6825e3c037b81/tiktoken-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5376b6f8dc4753cd81ead935c5f518fa0fbe7e133d9e25f648d8c4dabdd4bad7", size = 1236520 }, + { url = "https://files.pythonhosted.org/packages/40/59/14b20465f1d1cb89cfbc96ec27e5617b2d41c79da12b5e04e96d689be2a7/tiktoken-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:18228d624807d66c87acd8f25fc135665617cab220671eb65b50f5d70fa51f69", size = 883849 }, ] [[package]] name = "tqdm" -version = "4.66.5" +version = "4.67.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "platform_system == 'Windows'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/4f/0153c21dc5779a49a0598c445b1978126b1344bab9ee71e53e44877e14e0/tqdm-4.67.0.tar.gz", hash = "sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a", size = 169739 } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351 }, + { url = "https://files.pythonhosted.org/packages/2b/78/57043611a16c655c8350b4c01b8d6abfb38cc2acb475238b62c2146186d7/tqdm-4.67.0-py3-none-any.whl", hash = "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be", size = 78590 }, ] [[package]] diff --git a/libraries/python/skills/skills/document-skill/README.md b/libraries/python/skills/skills/document-skill/README.md index e69de29b..cb86b9a0 100644 --- a/libraries/python/skills/skills/document-skill/README.md +++ b/libraries/python/skills/skills/document-skill/README.md @@ -0,0 +1,5 @@ +# Document Skill + +## _IMPORTANT!_ + +This skill is not yet functional. It is a WIP as we figure out patterns for skill routines! diff --git a/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/__init__.py b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/__init__.py new file mode 100644 index 00000000..58a4b776 --- /dev/null +++ b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/__init__.py @@ -0,0 +1,11 @@ +from .draft_content import draft_content +from .draft_outline import draft_outline +from .get_user_feedback_for_outline_decision import get_user_feedback_for_outline_decision +from .get_user_feedback_for_page_decision import get_user_feedback_for_page_decision + +__all__ = [ + "draft_content", + "draft_outline", + "get_user_feedback_for_outline_decision", + "get_user_feedback_for_page_decision", +] diff --git a/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/draft_content.py b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/draft_content.py new file mode 100644 index 00000000..ccbb9ea5 --- /dev/null +++ b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/draft_content.py @@ -0,0 +1,76 @@ +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider +from openai_client.messages import format_with_liquid + +from ..document_skill import Outline, Paper + + +async def draft_content( + session_id: str, + open_ai_client: AsyncOpenAI | AsyncAzureOpenAI, + chat_history: str, + attachments: list, + outline_versions: list[Outline] = [], + paper_versions: list[Paper] = [], + user_feedback: str | None = None, + decision: str | None = None, +): + history = InMemoryMessageHistoryProvider(formatter=format_with_liquid) + + if decision == "[ITERATE]": + history.append_system_message( + ( + "Following the structure of the outline, iterate on the currently drafted page of the" + " document. It's more important to maintain an appropriately useful level of detail. " + " After this page is iterated upon, the system will follow up" + " and ask for the next page." + ) + ) + + else: + history.append_system_message( + ( + "Following the structure of the outline, create the content for the next (or first) page of the" + " document - don't try to create the entire document in one pass nor wrap it up too quickly, it will be a" + " multi-page document so just create the next page. It's more important to maintain" + " an appropriately useful level of detail. After this page is generated, the system will follow up" + " and ask for the next page. If you have already generated all the pages for the" + " document as defined in the outline, return empty content." + ) + ) + + history.append_system_message("{{chat_history}}", {"chat_history": chat_history}) + + for item in attachments: + history.append_system_message( + "{{item.filename}}{{item.content}}", + {"item": item}, + ) + + if outline_versions: + last_outline = outline_versions[-1] + history.append_system_message( + "{{last_outline}}", {"last_outline": last_outline} + ) + + if paper_versions: + if decision == "[ITERATE]" and user_feedback: + content = paper_versions[-1].contents[-1].content + history.append_system_message("{{content}}", {"content": content}) + history.append_system_message( + "{{user_feedback}}", {"user_feedback": user_feedback} + ) + else: + full_content = "" + for content in paper_versions[-1].contents: + full_content += content.content + history.append_system_message("{{content}}", {"content": content}) + + config = ChatDriverConfig( + openai_client=open_ai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/draft_outline.py b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/draft_outline.py new file mode 100644 index 00000000..8e7f6a24 --- /dev/null +++ b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/draft_outline.py @@ -0,0 +1,54 @@ +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider +from openai_client.messages import format_with_liquid + +from ..document_skill import Outline + + +async def draft_outline( + session_id: str, + open_ai_client: AsyncOpenAI | AsyncAzureOpenAI, + chat_history: str, + attachments: list, + outline_versions: list[Outline] = [], + user_feedback: str | None = None, +): + history = InMemoryMessageHistoryProvider(formatter=format_with_liquid) + + history.append_system_message( + ( + "Generate an outline for the document, including title. The outline should include the key points that will" + " be covered in the document. Consider the attachments, the rationale for why they were uploaded, and the" + " conversation that has taken place. The outline should be a hierarchical structure with multiple levels of" + " detail, and it should be clear and easy to understand. The outline should be generated in a way that is" + " consistent with the document that will be generated from it." + ) + ) + + history.append_system_message("{{chat_history}}", {"chat_history": chat_history}) + + for item in attachments: + history.append_system_message( + "{{item.filename}}{{item.content}}", + {"item": item}, + ) + + if len(outline_versions): + last_outline = outline_versions[-1] + history.append_system_message( + "{{last_outline}}", {"last_outline": last_outline} + ) + + if user_feedback is not None: + history.append_system_message( + "{{user_feedback}}", {"user_feedback": user_feedback} + ) + + config = ChatDriverConfig( + openai_client=open_ai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/get_user_feedback_for_outline_decision.py b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/get_user_feedback_for_outline_decision.py new file mode 100644 index 00000000..2015d908 --- /dev/null +++ b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/get_user_feedback_for_outline_decision.py @@ -0,0 +1,73 @@ +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider +from openai_client.messages import format_with_liquid + +from ..document_skill import Outline, Paper + + +async def get_user_feedback_for_outline_decision( + session_id: str, + open_ai_client: AsyncOpenAI | AsyncAzureOpenAI, + chat_history: str, + outline_versions: list[Outline] = [], + paper_versions: list[Paper] = [], + user_feedback: str | None = None, + outline: bool = False, +): + history = InMemoryMessageHistoryProvider(formatter=format_with_liquid) + + if outline: + history.append_system_message( + ( + "Use the user's most recent feedback to determine if the user wants to iterate further on the" + " provided outline [ITERATE], or if the user is ready to move on to drafting a paper from the" + " provided outline [CONTINUE]. Based on the user's feedback on the provided outline, determine if" + " the user wants to [ITERATE], [CONTINUE], or [QUIT]. Reply with ONLY [ITERATE], [CONTINUE], or [QUIT]." + ) + ) + else: + history.append_system_message( + ( + "You are an AI assistant that helps draft outlines for a future flushed-out document." + " You use the user's most recent feedback to determine if the user wants to iterate further on the" + " provided draft content [ITERATE], or if the user is ready to move on to drafting new additional content" + " [CONTINUE]. Based on the user's feedback on the provided drafted content, determine if" + " the user wants to [ITERATE], [CONTINUE], or [QUIT]. Reply with ONLY [ITERATE], [CONTINUE], or [QUIT]." + ) + ) + + history.append_system_message("{{chat_history}}", {"chat_history": chat_history}) + + if len(outline_versions): + last_outline_content = outline_versions[-1].content + if outline: + history.append_system_message( + "{{outline}}", {"outline": last_outline_content} + ) + else: + history.append_system_message( + "{{outline}}", {"outline": last_outline_content} + ) + + if not outline: + if paper_versions: + full_paper_content = "" + for content in paper_versions[-1].contents: + full_paper_content += content.content + history.append_system_message( + "{{content}}", {"content": full_paper_content} + ) + + if user_feedback is not None: + history.append_system_message( + "{{user_feedback}}", {"user_feedback": user_feedback} + ) + + config = ChatDriverConfig( + openai_client=open_ai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/get_user_feedback_for_page_decision.py b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/get_user_feedback_for_page_decision.py new file mode 100644 index 00000000..e710e7d3 --- /dev/null +++ b/libraries/python/skills/skills/document-skill/document_skill/chat_drivers/get_user_feedback_for_page_decision.py @@ -0,0 +1,73 @@ +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider +from openai_client.messages import format_with_liquid + +from ..document_skill import Outline, Paper + + +async def get_user_feedback_for_page_decision( + session_id: str, + open_ai_client: AsyncOpenAI | AsyncAzureOpenAI, + chat_history: str, + outline_versions: list[Outline] = [], + paper_versions: list[Paper] = [], + user_feedback: str | None = None, + outline: bool = False, +): + history = InMemoryMessageHistoryProvider(formatter=format_with_liquid) + + if outline: + history.append_system_message( + ( + "Use the user's most recent feedback to determine if the user wants to iterate further on the" + " provided outline [ITERATE], or if the user is ready to move on to drafting a paper from the" + " provided outline [CONTINUE]. Based on the user's feedback on the provided outline, determine if" + " the user wants to [ITERATE], [CONTINUE], or [QUIT]. Reply with ONLY [ITERATE], [CONTINUE], or [QUIT]." + ) + ) + else: + history.append_system_message( + ( + "You are an AI assistant that helps draft outlines for a future flushed-out document." + " You use the user's most recent feedback to determine if the user wants to iterate further on the" + " provided draft content [ITERATE], or if the user is ready to move on to drafting new additional content" + " [CONTINUE]. Based on the user's feedback on the provided drafted content, determine if" + " the user wants to [ITERATE], [CONTINUE], or [QUIT]. Reply with ONLY [ITERATE], [CONTINUE], or [QUIT]." + ) + ) + + history.append_system_message("{{chat_history}}", {"chat_history": chat_history}) + + if len(outline_versions): + last_outline_content = outline_versions[-1].content + if outline: + history.append_system_message( + "{{outline}}", {"outline": last_outline_content} + ) + else: + history.append_system_message( + "{{outline}}", {"outline": last_outline_content} + ) + + if not outline: + if paper_versions: + full_paper_content = "" + for content in paper_versions[-1].contents: + full_paper_content += content.content + history.append_system_message( + "{{content}}", {"content": full_paper_content} + ) + + if user_feedback is not None: + history.append_system_message( + "{{user_feedback}}", {"user_feedback": user_feedback} + ) + + config = ChatDriverConfig( + openai_client=open_ai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/document-skill/document_skill/document_skill.py b/libraries/python/skills/skills/document-skill/document_skill/document_skill.py index b57d8f21..b825c065 100644 --- a/libraries/python/skills/skills/document-skill/document_skill/document_skill.py +++ b/libraries/python/skills/skills/document-skill/document_skill/document_skill.py @@ -1,9 +1,15 @@ -from chat_driver import ChatDriverConfig -from context import Context +# flake8: noqa +# ruff: noqa + +from openai_client.chat_driver import ChatDriverConfig +from openai import AsyncAzureOpenAI, AsyncOpenAI from pydantic import BaseModel # temp to have something to experiment with -from skill_library import RoutineTypes, Skill +from skill_library import EmitterType, RoutineTypes, Skill from skill_library.routine import InstructionRoutine, ProgramRoutine +from .chat_drivers import draft_content, draft_outline +from .chat_drivers.get_user_feedback_for_outline_decision import get_user_feedback_for_outline_decision + NAME = "document_skill" DESCRIPTION = "Anything related to documents - creation, edit, translation" @@ -28,58 +34,40 @@ class Paper(BaseModel): contents: list[Content] = [] -class DocumentSkillContext(Context): +class DocumentSkillContext: def __init__(self) -> None: + # TODO: Pull in all this state from a Drive. self.chat_history: str = "" self.attachments_list: list[Attachment] = [] self.outline_versions: list[Outline] = [] self.paper_versions: list[Paper] = [] - super().__init__() # currently all params of Context can be None. - class DocumentSkill(Skill): def __init__( - self, - context: Context, - chat_driver_config: ChatDriverConfig, + self, emit: EmitterType, chat_driver_config: ChatDriverConfig, openaai_client: AsyncAzureOpenAI | AsyncOpenAI ) -> None: + self.openai_client = openaai_client self.document_skill_context: DocumentSkillContext = DocumentSkillContext() actions = [ - self.test_action, + # self.ask_user, + self.draft_content, + self.draft_outline, + self.get_user_feedback_decision, ] - # however, with current skill chat driver config (no chat connection setup), - # i see an issue when running the instruction routine as a command. It correctly detects the - # test_action step to run... but the I get the response: "Error running function: 'NoneType' object has no attribute 'chat'" - # it looks like information events after actions run in posix command are passed through a chat driver with model - # connectivity... as it explains the result in a natural language response wrapper (not the simple result of directly calling each command). - # i'm wondering if this is where the skill chat driver gets used as a chat connection? - # YES. look at instruction_routine_runner. This is how it is designed currently. - # i.r.r. will use the configured chat driver of the routine's skill to call "respond" with message being the "step" (the function in the routine)... - # but this means it is passed to the language model with the tool choices (assuming the action/function has also been registered as a function not just - # as a command... so that it can be included in the tool selection for the skill's chat driver to pass to the language model). Our code will execute (code in the - # chat driver... i think... i need to look into chat driver code more...) but the wrapped response out of the respond function is returned and messaged - # out as an information event. - # so big Q here... should a program routine or instruction routine be run by a language model? I'm not sure I want this... can we have an option? routines: list[RoutineTypes] = [ self.test_instruction_routine(), ] # Configure the skill's chat driver. - ##### - # EVEN IF, the skill will not use the chat driver for any llm call, right now - # the skill's chat driver is used to forward available skill commands and functions to - # any higher level assistant that has those skills registered. - ##### chat_driver_config.commands = actions chat_driver_config.functions = actions super().__init__( name=NAME, description=DESCRIPTION, - context=context, chat_driver_config=chat_driver_config, skill_actions=actions, routines=routines, @@ -97,6 +85,7 @@ def test_instruction_routine(self) -> InstructionRoutine: name="test_instruction_routine", description="Description of what the routine does.", routine=("test_action"), + skill=self, ) def template_example_routine(self) -> ProgramRoutine: @@ -107,308 +96,94 @@ def template_example_routine(self) -> ProgramRoutine: name="template_example_routine", description="Description of what the routine does.", program=("TBD"), + skill=self, ) ################################## # Actions ################################## - def test_action( + async def draft_content( self, - context: Context, + session_id: str, + decision: str | None = None, + user_feedback: str | None = None, ) -> str: - return "hello world" # - - # This may not be needed as an action... not sure yet how context will be set. - + response = await draft_content( + session_id=session_id, + open_ai_client=self.openai_client, + chat_history=self.document_skill_context.chat_history, + attachments=self.document_skill_context.attachments_list, + outline_versions=self.document_skill_context.outline_versions, + paper_versions=self.document_skill_context.paper_versions, + user_feedback=user_feedback, + decision=decision, + ) + return response.message or "" -# def set_context( -# self, -# context: Context, -# chat_history: str, -# attachments_list: list[Attachment] -# ) -> None: -# self.context.chat_history = chat_history -# self.context.attachments_list = attachments_list -# -# async def draft_outline( -# self, -# context: Context, -# openai_client: AsyncOpenAI, -# model: str, -# user_feedback: str | None = None -# ) -> None: -# -# # construct completion request - draft outline from existing info -# messages: list[ChatCompletionMessageParam] = [] -# _add_main_system_message(messages, draft_outline_main_system_message) -# _add_chat_history_system_message(messages, self.context.chat_history) -# _add_attachments_system_message(messages, self.context.attachments_list) -# -# if len(self.context.outline_versions) != 0: -# _add_existing_outline_system_message(messages, self.context.outline_versions[-1].content) -# -# if user_feedback is not None: -# _add_user_feedback_system_message(messages, user_feedback) -# -# completion_args = { -# "messages": messages, -# "model": model, -# "response_format": { -# "type": "text" -# } -# } -# completion = await openai_client.chat.completions.create(**completion_args) -# outline = completion.choices[0].message.content -# -# debug_log = json.dumps(completion_args, indent=2) + "\n\n" -# debug_log += f"Response:\n{completion.model_dump_json(indent=2)}\n\n" -# #print(debug_log) -# -# # message event should be sent from within this action. For now using stdout. -# chat_output = f"\nAssistant: Outline drafted\n\n{outline}\n" -# sys.stdout.write(chat_output) -# sys.stdout.flush() -# -# # store generated outline -# version_no = len(self.context.outline_versions) + 1 -# self.context.outline_versions.append(Outline(version=version_no, content=outline)) -# -# # update chat_history. -# self.context.chat_history = self.context.chat_history + chat_output -# return -# -# async def draft_content( -# self, -# context: Context, -# openai_client: AsyncOpenAI, -# model: str, -# user_feedback: str | None = None, -# decision: str | None = None -# ) -> None: -# # construct completion request - draft content/page/fragment from existing info -# messages: list[ChatCompletionMessageParam] = [] -# -# if decision == "[ITERATE]": -# _add_main_system_message(messages, draft_page_iterate_main_system_message) -# else: -# _add_main_system_message(messages, draft_page_continue_main_system_message) -# -# _add_chat_history_system_message(messages, self.context.chat_history) -# _add_attachments_system_message(messages, self.context.attachments_list) -# _add_approved_outline_system_message(messages, self.context.outline_versions[-1].content) -# -# if len(self.context.paper_versions) != 0: -# if decision == "[ITERATE]" and user_feedback is not None: -# _add_existing_content_system_message(messages, self.context.paper_versions[-1].contents[-1].content) -# _add_user_feedback_system_message(messages, user_feedback) -# else: -# full_content = "" -# for content in self.context.paper_versions[-1].contents: -# full_content += content.content -# _add_existing_content_system_message(messages, full_content) -# -# completion_args = { -# "messages": messages, -# "model": model, -# "response_format": { -# "type": "text" -# } -# } -# completion = await openai_client.chat.completions.create(**completion_args) -# draft = completion.choices[0].message.content -# -# debug_log = json.dumps(completion_args, indent=2) + "\n\n" -# debug_log += f"Response:\n{completion.model_dump_json(indent=2)}\n\n" -# #print(debug_log) -# -# # message event should be sent from within this action. For now using stdout. -# if decision == "[ITERATE]": -# chat_output = f"\nAssistant: Updated page based on your feedback:\n\n{draft}\n" -# else: -# chat_output = f"\nAssistant: Page drafted:\n\n{draft}\n" -# sys.stdout.write(chat_output) -# sys.stdout.flush() -# -# # store generated content/page/fragment -# version_no = len(self.context.paper_versions) + 1 -# if len(self.context.paper_versions) == 0: -# self.context.paper_versions.append(Paper(version=version_no, contents=[Content(content=draft)])) -# else: -# existing_contents = self.context.paper_versions[-1].contents -# if decision == "[ITERATE]": -# existing_contents[-1].content = draft -# else: -# existing_contents.append(Content(content=draft)) -# self.context.paper_versions.append(Paper(version=version_no, contents=existing_contents)) -# -# # update chat_history. -# self.context.chat_history = self.context.chat_history + chat_output -# return -# -# async def get_user_feedback( -# self, -# context: Context, -# openai_client: AsyncOpenAI, -# model: str, -# outline: bool -# ) -> tuple[str, str]: -# -# # message event should be sent from within this action. For now using stdout. -# event_message = ("Please review the above draft and provide me any feedback. You can also " -# "let me know if you are ready to continue drafting the paper or would like" -# " to iterate on the current content.") -# # msg_event = MessageEvent(message=event_message) -# chat_output = f"\nAssistant: {event_message}\n" -# sys.stdout.write(chat_output) -# sys.stdout.flush() -# self.context.chat_history = self.context.chat_history + chat_output -# -# #user response input should be awaited for here from interface. For now using stdout. -# sys.stdout.write("\nUser: ") -# user_feedback = input() -# sys.stdout.write(user_feedback) -# sys.stdout.flush() -# chat_output = f"\nUser: {user_feedback}" -# self.context.chat_history = self.context.chat_history + chat_output -# -# # construct completion request - determine user intent from feedback -# messages: list[ChatCompletionMessageParam] = [] -# if outline is True: -# _add_main_system_message(messages, outline_feedback_main_system_message) -# else: -# _add_main_system_message(messages, draft_page_feedback_main_system_message) -# -# _add_chat_history_system_message(messages, self.context.chat_history) -# -# if len(self.context.outline_versions) != 0: -# if outline is True: -# _add_existing_outline_system_message(messages, self.context.outline_versions[-1].content) -# else: -# _add_approved_outline_system_message(messages, self.context.outline_versions[-1].content) -# -# if outline is False: -# if len(self.context.paper_versions) != 0: -# full_content = "" -# for content in self.context.paper_versions[-1].contents: -# full_content += content.content -# _add_existing_content_system_message(messages, full_content) -# -# _add_user_feedback_system_message(messages, user_feedback) -# -# completion_args = { -# "messages": messages, -# "model": model, -# "response_format": { -# "type": "text" -# } -# } -# completion = await openai_client.chat.completions.create(**completion_args) -# decision = completion.choices[0].message.content # this should NOT be done as a string. -# -# debug_log = json.dumps(completion_args, indent=2) + "\n\n" -# debug_log += f"Response:\n{completion.model_dump_json(indent=2)}\n\n" -# #print(debug_log) -# -# return decision, user_feedback -# -# # A little helper -# def print_final_doc(self, context: Context) -> None: -# print("Assistant: Looks like we have a completed doc. Here it is!\n") -# # compose full content for existing paper (the latest version) -# full_content = "" -# for content in self.context.paper_versions[-1].contents: -# full_content += content.content -# print(full_content) -# -# -## Current approach uses system messages for everything. -# draft_outline_main_system_message = ("Generate an outline for the document, including title. The outline should include the key points that will" -# " be covered in the document. Consider the attachments, the rationale for why they were uploaded, and the" -# " conversation that has taken place. The outline should be a hierarchical structure with multiple levels of" -# " detail, and it should be clear and easy to understand. The outline should be generated in a way that is" -# " consistent with the document that will be generated from it.") -# #("You are an AI assistant that helps draft outlines for a future flushed-out document." -# # " You use information from a chat history between a user and an assistant, a prior version of a draft" -# # " outline if it exists, as well as any other attachments provided by the user to inform a newly revised " -# # "outline draft. Provide ONLY any outline. Provide no further instructions to the user.") -# -# outline_feedback_main_system_message = ("Use the user's most recent feedback to determine if the user wants to iterate further on the" -# " provided outline [ITERATE], or if the user is ready to move on to drafting a paper from the" -# " provided outline [CONTINUE]. Based on the user's feedback on the provided outline, determine if" -# " the user wants to [ITERATE], [CONTINUE], or [QUIT]. Reply with ONLY [ITERATE], [CONTINUE], or [QUIT].") -# -# draft_page_continue_main_system_message = ("Following the structure of the outline, create the content for the next (or first) page of the" -# " document - don't try to create the entire document in one pass nor wrap it up too quickly, it will be a" -# " multi-page document so just create the next page. It's more important to maintain" -# " an appropriately useful level of detail. After this page is generated, the system will follow up" -# " and ask for the next page. If you have already generated all the pages for the" -# " document as defined in the outline, return empty content.") -# #("You are an AI assistant that helps draft new content of a document based on an outline." -# # " You use information from a chat history between a user and an assistant, the approved outline from the user," -# # "and an existing version of drafted content if it exists, as well as any other attachments provided by the user to inform newly revised " -# # "content. Newly drafted content does not need to cover the entire outline. Instead it should be limited to a reasonable 100 lines of natural language" -# # " or subsection of the outline (which ever is shorter). The newly drafted content should be written as to append to any existing drafted content." -# # " This way the user can review newly drafted content as a subset of the future full document and not be overwhelmed." -# # "Only provide the newly drafted content. Provide no further instructions to the user.") -# -# draft_page_iterate_main_system_message = ("Following the structure of the outline, iterate on the currently drafted page of the" -# " document. It's more important to maintain" -# " an appropriately useful level of detail. After this page is iterated upon, the system will follow up" -# " and ask for the next page.") -# -# draft_page_feedback_main_system_message = ("You are an AI assistant that helps draft outlines for a future flushed-out document." -# " You use the user's most recent feedback to determine if the user wants to iterate further on the" -# " provided draft content [ITERATE], or if the user is ready to move on to drafting new additional content" -# " [CONTINUE]. Based on the user's feedback on the provided drafted content, determine if" -# " the user wants to [ITERATE], [CONTINUE], or [QUIT]. Reply with ONLY [ITERATE], [CONTINUE], or [QUIT].") -# -# def _add_main_system_message(messages: list[ChatCompletionMessageParam], prompt: str) -> None: -# message: ChatCompletionSystemMessageParam = { -# "role": "system", -# "content": prompt -# } -# messages.append(message) -# -# def _add_chat_history_system_message(messages: list[ChatCompletionMessageParam], chat_history: str) -> None: -# message: ChatCompletionSystemMessageParam = { -# "role": "system", -# "content": f"{chat_history}" -# } -# messages.append(message) -# -# def _add_attachments_system_message(messages: list[ChatCompletionMessageParam], attachments: list[Attachment]) -> None: -# for item in attachments: -# message: ChatCompletionSystemMessageParam = { -# "role": "system", -# "content": (f"{item.filename}{item.content}") -# } -# messages.append(message) -# -# def _add_existing_outline_system_message(messages: list[ChatCompletionMessageParam], outline: str) -> None: -# message: ChatCompletionSystemMessageParam = { -# "role": "system", -# "content": (f"{outline}") -# } -# messages.append(message) -# -# def _add_approved_outline_system_message(messages: list[ChatCompletionMessageParam], outline: str) -> None: -# message: ChatCompletionSystemMessageParam = { -# "role": "system", -# "content": (f"{outline}") -# } -# messages.append(message) -# -# def _add_existing_content_system_message(messages: list[ChatCompletionMessageParam], content: str) -> None: -# message: ChatCompletionSystemMessageParam = { -# "role": "system", -# "content": (f"{content}") -# } -# messages.append(message) -# -# def _add_user_feedback_system_message(messages: list[ChatCompletionMessageParam], user_feedback: str) -> None: -# message: ChatCompletionSystemMessageParam = { -# "role": "system", -# "content": (f"{user_feedback}") -# } -# messages.append(message) -# + async def draft_outline( + self, session_id: str, decision: str | None = None, user_feedback: str | None = None + ) -> str: + response = await draft_outline( + session_id=session_id, + open_ai_client=self.openai_client, + chat_history=self.document_skill_context.chat_history, + attachments=self.document_skill_context.attachments_list, + outline_versions=self.document_skill_context.outline_versions, + user_feedback=user_feedback, + ) + return response.message or "" + + async def get_user_feedback_decision(self, session_id: str, user_feedback: str, outline: bool) -> str: + response = await get_user_feedback_for_outline_decision( + session_id=session_id, + open_ai_client=self.openai_client, + chat_history=self.document_skill_context.chat_history, + outline_versions=self.document_skill_context.outline_versions, + paper_versions=self.document_skill_context.paper_versions, + user_feedback=user_feedback, + outline=outline, + ) + return response.message or "" + + async def routine(self, session_id: str): + # Define these vars here to make the following routine look more like a PROGRAM routine. + document_skill = self + + async def ask_user(question: str) -> str: + return "hello world" + + # Routine. + decision = "[ITERATE]" + while decision == "[ITERATE]": + await document_skill.draft_outline(session_id, user_feedback=user_feedback) + user_feedback = await ask_user("This look good?") + decision = await document_skill.get_user_feedback_decision(session_id, user_feedback, outline=True) + if decision == "[QUIT]": + exit() + await document_skill.draft_content(session_id) + user_feedback = await ask_user("This look good?") + decision = await document_skill.get_user_feedback_decision(session_id, user_feedback, outline=False) + while decision != "[QUIT]": + content = await document_skill.draft_content(session_id, user_feedback=user_feedback, decision=decision) + decision, user_feedback = await document_skill.get_user_feedback_decision( + session_id, user_feedback, outline=False + ) + return content + + # decision = "[ITERATE]" + # while decision == "[ITERATE]": + # document_skill.draft_outline(user_feedback=user_feedback, history=assistant.chat_history) + # guided_conversation.run() + # user_feedback = await ask_user("This look good?") + # decision = document_skill.get_user_feedback_decision(user_feedback, outline=True) + # if decision == "[QUIT]": + # exit() + # document_skill.draft_content() + # user_feedback = ask_user("This look good?") + # decision = document_skill.get_user_feedback_decision(user_feedback, outline=False) + # while decision != "[QUIT]": + # content = document_skill.draft_content(user_feedback=user_feedback, decision=decision) + # decision, user_feedback = await document_skill.get_user_feedback_decision( + # context, user_feedback, outline=False + # ) + # return content diff --git a/libraries/python/skills/skills/document-skill/pyproject.toml b/libraries/python/skills/skills/document-skill/pyproject.toml index 4e437203..63e162e9 100644 --- a/libraries/python/skills/skills/document-skill/pyproject.toml +++ b/libraries/python/skills/skills/document-skill/pyproject.toml @@ -6,20 +6,20 @@ authors = [{name="MADE:Explorers"}] readme = "README.md" requires-python = ">=3.11" dependencies = [ - "skill-library>=0.1.0", - "chat-driver>=0.1.0", "context>=0.1.0", "events>=0.1.0", + "openai-client>=0.1.0", + "skill-library>=0.1.0", ] [tool.uv] package = true [tool.uv.sources] -skill-library = { path = "../../skill-library", editable= true } -chat-driver = { path = "../../../chat-driver", editable = true } context = { path = "../../../context", editable = true } events = { path = "../../../events", editable = true } +openai-client = { path = "../../../openai-client", editable = true } +skill-library = { path = "../../skill-library", editable= true } [build-system] requires = ["hatchling"] diff --git a/libraries/python/skills/skills/document-skill/uv.lock b/libraries/python/skills/skills/document-skill/uv.lock index 07901257..e8e201a5 100644 --- a/libraries/python/skills/skills/document-skill/uv.lock +++ b/libraries/python/skills/skills/document-skill/uv.lock @@ -122,6 +122,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262 }, ] +[[package]] +name = "assistant-drive" +version = "0.1.0" +source = { editable = "../../../assistant-drive" } +dependencies = [ + { name = "context" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../../context" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.5.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -281,39 +306,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "../../../chat-driver" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../context" }, - { name = "events", editable = "../../../events" }, - { name = "function-registry", editable = "../../../function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../../../openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - [[package]] name = "click" version = "8.1.7" @@ -414,17 +406,17 @@ name = "document-skill" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../../chat-driver" }, { name = "context", editable = "../../../context" }, { name = "events", editable = "../../../events" }, + { name = "openai-client", editable = "../../../openai-client" }, { name = "skill-library", editable = "../../skill-library" }, ] @@ -548,40 +540,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../../function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -897,7 +855,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -910,7 +868,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../../function-registry" }, + { name = "events", editable = "../../../events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, @@ -1409,11 +1367,11 @@ name = "skill-library" version = "0.1.0" source = { editable = "../../skill-library" } dependencies = [ - { name = "chat-driver" }, + { name = "assistant-drive" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "openai" }, + { name = "openai-client" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -1423,11 +1381,11 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../../chat-driver" }, + { name = "assistant-drive", editable = "../../../assistant-drive" }, { name = "context", editable = "../../../context" }, { name = "events", editable = "../../../events" }, - { name = "function-registry", editable = "../../../function-registry" }, { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../../openai-client" }, { name = "pydantic", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.3.4" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1435,6 +1393,13 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "sniffio" version = "1.3.1" diff --git a/libraries/python/chat-driver/.vscode/settings.json b/libraries/python/skills/skills/form-filler-skill/.vscode/settings.json similarity index 99% rename from libraries/python/chat-driver/.vscode/settings.json rename to libraries/python/skills/skills/form-filler-skill/.vscode/settings.json index d02aad67..7177a8e5 100644 --- a/libraries/python/chat-driver/.vscode/settings.json +++ b/libraries/python/skills/skills/form-filler-skill/.vscode/settings.json @@ -48,6 +48,7 @@ "dotenv", "httpx", "openai", + "posix", "pydantic", "pypdf", "runtimes", diff --git a/libraries/python/chat-driver/Makefile b/libraries/python/skills/skills/form-filler-skill/Makefile similarity index 100% rename from libraries/python/chat-driver/Makefile rename to libraries/python/skills/skills/form-filler-skill/Makefile diff --git a/libraries/python/skills/skills/form-filler-skill/README.md b/libraries/python/skills/skills/form-filler-skill/README.md new file mode 100644 index 00000000..0e81e0de --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/README.md @@ -0,0 +1,6 @@ +# Form Filler Skill + +## _IMPORTANT!_ + +Nothing works here. This is a WIP as we figure out routine running patterns. + diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/__init__.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/__init__.py new file mode 100644 index 00000000..451e61dd --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/__init__.py @@ -0,0 +1,3 @@ +from form_filler_skill.form_filler_skill import FormFillerSkill + +export = FormFillerSkill diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/agenda.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/agenda.py new file mode 100644 index 00000000..9d5623e9 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/agenda.py @@ -0,0 +1,24 @@ +import logging + +from pydantic import Field + +from form_filler_skill.base_model_llm import BaseModelLLM +from form_filler_skill.resources import ( + ResourceConstraintMode, +) + +logger = logging.getLogger(__name__) + + +class AgendaItem(BaseModelLLM): + title: str = Field(description="Brief description of the item") + resource: int = Field(description="Number of turns required for the item") + + +class Agenda(BaseModelLLM): + resource_constraint_mode: ResourceConstraintMode | None = Field(default=None) + max_agenda_retries: int = Field(default=2) + items: list[AgendaItem] = Field( + description="Ordered list of items to be completed in the remainder of the conversation", + default_factory=list, + ) diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/artifact.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/artifact.py new file mode 100644 index 00000000..ab978ceb --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/artifact.py @@ -0,0 +1,446 @@ +# Copyright (c) Microsoft. All rights reserved. + + +import json +import logging +from typing import Any, Literal, TypeVar, Union, get_args, get_origin, get_type_hints + +from openai import AsyncAzureOpenAI, AsyncOpenAI +from pydantic import BaseModel, ValidationError, create_model + +from form_filler_skill.base_model_llm import BaseModelLLM + +from .chat_drivers.fix_artifact_error import fix_artifact_error +from .message import Conversation, ConversationMessageType, Message + +logger = logging.getLogger(__name__) + + +class Artifact: + """The Artifact plugin takes in a Pydantic base model, and robustly handles updating the fields of the model + A typical use case is as a form an agent must complete throughout a conversation. + Another use case is as a working memory for the agent. + + The primary interface is update_artifact, which takes in the field_name to update and its new value. + Additionally, the chat_history is passed in to help the agent make informed decisions in case an error occurs. + + The Artifact also exposes several functions to access internal state: + get_artifact_for_prompt, get_schema_for_prompt, and get_failed_fields. + """ + + def __init__( + self, + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + input_artifact: BaseModel, + max_artifact_field_retries: int = 2, + ) -> None: + """ + Initialize the Artifact plugin with the given Pydantic base model. + + Args: + input_artifact (BaseModel): The Pydantic base model to use as the artifact + max_artifact_field_retries (int): The maximum number of times to retry updating a field in the artifact + """ + + self.openai_client = openai_client + + self.max_artifact_field_retries = max_artifact_field_retries + + self.original_schema = input_artifact.model_json_schema() + + # Create a new artifact model based on the one provided by the user with + # "Unanswered" set for all fields. + modified_classes = self._modify_classes(input_artifact) + self.artifact = self._modify_base_artifact(input_artifact, modified_classes)() + + # failed_artifact_fields maps a field name to a list of the history of + # the failed attempts to update it. + # dict: key = field, value = list of tuple[attempt, error message] + self.failed_artifact_fields: dict[str, list[tuple[str, str]]] = {} + + async def update_artifact(self, field_name: str, field_value: Any) -> tuple[bool, Conversation]: + """ + The core interface for the Artifact plugin. This function will attempt + to update the given field_name to the given field_value. If the + field_value fails Pydantic validation, an LLM will determine one of two + actions to take. + + Given the conversation as additional context the two actions are: + - Retry the update the artifact by fixing the formatting using the + previous failed attempts as guidance + - Take no action or in other words, resume the conversation to ask + the user for more information because the user gave incomplete or + incorrect information + + Args: + field_name (str): The name of the field to update in the artifact + field_value (Any): The value to set the field to conversation + (Conversation): The conversation object that contains the history of + the conversation + + Returns: + A tuple with two fields: a boolean indicating + success and a list of conversation messages that may have been + generated. + + Several outcomes can happen: + + - The update may have failed due to: + - A field_name that is not valid in the artifact. + - The field_value failing Pydantic validation and all retries + failed. + - The model failed to correctly call a tool. + In this case, the boolean will be False and the list may contain + a message indicating the failure. + + - The agent may have successfully updated the artifact or fixed it. + In this case, the boolean will be True and the list will contain + a message indicating the update and possibly intermediate + messages. + + - The agent may have decided to resume the conversation. + In this case, the boolean will be True and the messages may only + contain messages indicated previous errors. + """ + + conversation = Conversation() + + # Check if the field name is valid, and return with a failure message if + # not. + is_valid_field, msg = self._is_valid_field(field_name) + if not is_valid_field: + if msg is not None: + conversation.messages.append(msg) + return False, conversation + + # Try to update the field, and handle any errors that occur until the + # field is successfully updated or skipped according to + # max_artifact_field_retries. + while True: + try: + # Check if there have been too many previous failed attempts to + # update the field. + if len(self.failed_artifact_fields.get(field_name, [])) >= self.max_artifact_field_retries: + logger.warning(f"Updating field {field_name} has failed too many times. Skipping.") + return False, conversation + + # Attempt to update the artifact. + self.artifact.__setattr__(field_name, field_value) + + # This will only be reached if there were no exceptions setting + # the artifact field. + msg = Message( + { + "role": "assistant", + "content": f"Assistant updated {field_name} to {field_value}", + }, + type=ConversationMessageType.ARTIFACT_UPDATE, + turn=None, + ) + conversation.messages.append(msg) + return True, conversation + + except Exception as e: + logger.warning(f"Error updating field {field_name}: {e}. Retrying...") + # Handle update error will increment failed_artifact_fields, once it has failed + # greater than self.max_artifact_field_retries the field will be skipped and the loop will break + success, new_field_value = await self._handle_update_error(field_name, field_value, conversation, e) + + # The agent has successfully fixed the field. + if success: + if new_field_value is not None: + logger.info(f"Agent successfully fixed field {field_name}. New value: {new_field_value}") + field_value = new_field_value + else: + # This is the case where the agent has decided to resume the conversation. + logger.info( + f"Agent could not fix the field itself & decided to resume conversation to fix field {field_name}" + ) + return True, conversation + + logger.warning(f"Agent failed to fix field {field_name}. Retrying...") + # Otherwise, the agent has failed and we will go through the loop again + + def get_artifact_for_prompt(self) -> str: + """ + Returns a formatted JSON-like representation of the current state of the + fields artifact. Any fields that were failed are completely omitted. + """ + failed_fields = self.get_failed_fields() + return json.dumps({k: v for k, v in self.artifact.model_dump().items() if k not in failed_fields}) + + def get_schema_for_prompt(self, filter_one_field: str | None = None) -> str: + """Gets a clean version of the original artifact schema, optimized for use in an LLM prompt. + + Args: + filter_one_field (str | None): If this is provided, only the schema for this one field will be returned. + + Returns: + str: The cleaned schema + """ + + def _clean_properties(schema: dict, failed_fields: list[str]) -> str: + properties = schema.get("properties", {}) + clean_properties = {} + for name, property_dict in properties.items(): + if name not in failed_fields: + cleaned_property = {} + for k, v in property_dict.items(): + if k in ["title", "default"]: + continue + cleaned_property[k] = v + clean_properties[name] = cleaned_property + + clean_properties_str = str(clean_properties) + clean_properties_str = clean_properties_str.replace("$ref", "type") + clean_properties_str = clean_properties_str.replace("#/$defs/", "") + return clean_properties_str + + # If filter_one_field is provided, only get the schema for that one field + if filter_one_field: + if not self._is_valid_field(filter_one_field): + logger.error(f'Field "{filter_one_field}" is not a valid field in the artifact.') + raise ValueError(f'Field "{filter_one_field}" is not a valid field in the artifact.') + filtered_schema = {"properties": {filter_one_field: self.original_schema["properties"][filter_one_field]}} + filtered_schema.update((k, v) for k, v in self.original_schema.items() if k != "properties") + schema = filtered_schema + else: + schema = self.original_schema + + failed_fields = self.get_failed_fields() + properties = _clean_properties(schema, failed_fields) + if not properties: + logger.error("No properties found in the schema.") + raise ValueError("No properties found in the schema.") + + types_schema = schema.get("$defs", {}) + custom_types = [] + for type_name, type_info in types_schema.items(): + if f"'type': '{type_name}'" in properties: + clean_schema = _clean_properties(type_info, []) + if clean_schema != "{}": + custom_types.append(f"{type_name} = {clean_schema}") + + if custom_types: + explanation = ( + f"If you wanted to create a {type_name} object, for example, you " + "would make a JSON object with the following keys: " + "{', '.join(types_schema[type_name]['properties'].keys())}." + ) + custom_types_str = "\n".join(custom_types) + return ( + f"{properties}\n\n" + "Here are the definitions for the custom types referenced in the artifact schema:\n" + f"{custom_types_str}\n\n" + f"{explanation}\n" + "Remember that when updating the artifact, the field will be the original " + "field name in the artifact and the JSON object(s) will be the value." + ) + else: + return properties + + def get_failed_fields(self) -> list[str]: + """Get a list of fields that have failed all attempts to update. + + Returns: + list[str]: A list of field names that have failed all attempts to update. + """ + fields = [] + for field, attempts in self.failed_artifact_fields.items(): + if len(attempts) >= self.max_artifact_field_retries: + fields.append(field) + return fields + + T = TypeVar("T") + + def _get_type_if_subtype(self, target_type: type[T], base_type: type[Any]) -> type[T] | None: + """ + Recursively checks the target_type to see if it is a subclass of + base_type or a generic including base_type. + + Args: + target_type: The type to check. + base_type: The type to check against. + + Returns: + The class type if target_type is base_type, a subclass of base_type, + or a generic including base_type; otherwise, None. + """ + origin = get_origin(target_type) + if origin is None: + if issubclass(target_type, base_type): + return target_type + else: + # Recursively check if any of the arguments are the target type. + for arg in get_args(target_type): + result = self._get_type_if_subtype(arg, base_type) + if result is not None: + return result + return None + + def _modify_classes(self, artifact_class: BaseModel) -> dict[str, type[BaseModelLLM]]: + """Find all classes used as type hints in the artifact, and modify them to set 'Unanswered' as a default and valid value for all fields.""" + modified_classes = {} + # Find any instances of BaseModel in the artifact class in the first "level" of type hints + for field_name, field_type in get_type_hints(artifact_class).items(): + is_base_model = self._get_type_if_subtype(field_type, BaseModel) + if is_base_model is not None: + modified_classes[field_name] = self._modify_base_artifact(is_base_model) + + return modified_classes + + def _replace_type_annotations( + self, field_annotation: type[Any] | None, modified_classes: dict[str, type[BaseModelLLM]] + ) -> type: + """ + Recursively replace type annotations with modified classes where + applicable. + """ + # Get the origin of the field annotation, which is the base type for + # generic types (e.g., List[str] -> list, Dict[str, int] -> dict) + origin = get_origin(field_annotation) + + # Get the type arguments of the generic type (e.g., List[str] -> str, + # Dict[str, int] -> str, int) + args = get_args(field_annotation) + + if origin is None: + # The type is not generic; check if it's a subclass that needs to be replaced + if isinstance(field_annotation, type) and issubclass(field_annotation, BaseModelLLM): + return modified_classes.get(field_annotation.__name__, field_annotation) + return field_annotation if field_annotation is not None else object + else: + # The type is generic; recursively replace the type annotations of the arguments + new_args = tuple(self._replace_type_annotations(arg, modified_classes) for arg in args) + return origin[new_args] + + def _modify_base_artifact( + self, + artifact_model: BaseModel, + modified_classes: dict[str, type[BaseModelLLM]] | None = None, + ) -> type[BaseModelLLM]: + """ + Create a new artifact model with 'Unanswered' as a default and valid + value for all fields. + """ + field_definitions = {} + for name, field_info in artifact_model.model_fields.items(): + # Replace original classes with modified version. + if modified_classes is not None: + field_info.annotation = self._replace_type_annotations(field_info.annotation, modified_classes) + + # This makes it possible to always set a field to "Unanswered". + annotation = Union[field_info.annotation, Literal["Unanswered"]] + + # This sets the default value to "Unanswered". + default = "Unanswered" + + # This adds "Unanswered" as a possible value to any regex patterns. + metadata = field_info.metadata + for m in metadata: + if hasattr(m, "pattern"): + m.pattern += "|Unanswered" + + field_definitions[name] = (annotation, default, *metadata) + + return create_model("Artifact", __base__=BaseModelLLM, **field_definitions) + + def _is_valid_field(self, field_name: str) -> tuple[bool, Message | None]: + """ + Check if the field_name is a valid field in the artifact. Returns True + if it is, False and an error message otherwise. + """ + if field_name not in self.artifact.model_fields: + error_message = f'Field "{field_name}" is not a valid field in the artifact.' + msg = Message( + {"role": "assistant", "content": error_message}, + type=ConversationMessageType.ARTIFACT_UPDATE, + turn=None, + ) + return False, msg + return True, None + + async def _handle_update_error( + self, field_name: str, field_value: Any, conversation: Conversation, error: Exception + ) -> tuple[bool, Any]: + """ + Handles the logic for when an error occurs while updating a field. + Creates the appropriate context for the model and calls the LLM to fix + the error. + + Args: + field_name (str): The name of the field to update in the artifact + field_value (Any): The value to set the field to conversation + (Conversation): The conversation object that contains the history of + the conversation error (Exception): The error that occurred while + updating the field + + Returns: + tuple[bool, Any]: A tuple containing a boolean indicating success + and the new field value if successful (if not, then None) + """ + + # Keep track of history of failed attempts for each field. + previous_attempts = self.failed_artifact_fields.get(field_name, []) + error_str = ( + str(error) + if not isinstance(error, ValidationError) + else "; ".join([e.get("msg") for e in error.errors()]).replace( + "; Input should be 'Unanswered'", " or input should be 'Unanswered'" + ) + ) + attempt = (str(field_value), error_str) + self.failed_artifact_fields[field_name] = previous_attempts + [attempt] + + result = await fix_artifact_error( + self.openai_client, + previous_attempts="\n".join([ + f"Attempt: {attempt}\nError: {error}" for attempt, error in previous_attempts + ]), + artifact_schema=self.get_schema_for_prompt(filter_one_field=field_name), + conversation=conversation, + field_name=field_name, + ) + + # Handling the result of the LLM call + if result.message not in ["UPDATE_ARTIFACT", "RESUME_CONVERSATION"]: + logger.warning( + f"Failed to fix the artifact error due to an invalid response from the LLM: {result.message}" + ) + return False, None + + if result.message == "RESUME_CONVERSATION": + return True, None + + if result.message.startswith("UPDATE_ARTIFACT("): + field_value = result.message.split("(")[1].split(")")[0] + return True, field_value + + logger.warning(f"Failed to fix the artifact error due to an invalid response from the LLM: {result.message}") + return False, None + + def to_json(self) -> dict: + artifact_fields = self.artifact.model_dump() + return { + "artifact": artifact_fields, + "failed_fields": self.failed_artifact_fields, + } + + @classmethod + def from_json( + cls, + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + json_data: dict, + input_artifact: BaseModel, + max_artifact_field_retries: int = 2, + ) -> "Artifact": + artifact = cls(openai_client, input_artifact, max_artifact_field_retries) + + artifact.failed_artifact_fields = json_data["failed_fields"] + + # Iterate over artifact fields and set them to the values in the json data + # Skip any fields that are set as "Unanswered" + for field_name, field_value in json_data["artifact"].items(): + if field_value != "Unanswered": + setattr(artifact.artifact, field_name, field_value) + return artifact diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/base_model_llm.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/base_model_llm.py new file mode 100644 index 00000000..06407695 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/base_model_llm.py @@ -0,0 +1,53 @@ +# Copyright (c) Microsoft. All rights reserved. + +import ast +from types import NoneType +from typing import Any, get_args + +from pydantic import BaseModel, ValidationInfo, field_validator + + +class BaseModelLLM(BaseModel): + """ + A Pydantic base class for use when an LLM is completing fields. Provides a + custom field validator and Pydantic Config. + """ + + @field_validator("*", mode="before") + def parse_literal_eval(cls, value: str, info: ValidationInfo) -> NoneType | str | Any: + """ + An LLM will always result in a string (e.g. '["x", "y"]'), so we need to + parse it to the correct type. + """ + + # Get the type hints for the field. We know the field is present because + # pydantic gave it to us, so we can ignore this type error. + annotation = cls.model_fields[info.field_name].annotation # type: ignore + typehints = get_args(annotation) + if len(typehints) == 0: + typehints = [annotation] + + # Usually fields that are NoneType have another type hint as well, e.g. + # str | None. If the LLM returns "None" and the field allows NoneType, + # we should return None without this code, the next if-block would leave + # the string "None" as the value + if (NoneType in typehints) and (value == "None"): + return None + + # If the field allows strings, we don't parse it - otherwise a + # validation error might be raised e.g. phone_number = "1234567890" + # should not be converted to an int if the type hint is str + if str in typehints: + return value + try: + evaluated_value = ast.literal_eval(value) + return evaluated_value + except Exception: + return value + + class Config: + # Ensure that validation happens every time a field is updated, not just + # when the artifact is created. + validate_assignment = True + # Do not allow extra fields to be added to the artifact. + extra = "forbid" diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/extract_form_fields.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/extract_form_fields.py new file mode 100644 index 00000000..26839318 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/extract_form_fields.py @@ -0,0 +1,74 @@ +from typing import Any, Literal + +import openai_client +from openai import AsyncOpenAI +from openai.types.chat import ChatCompletionMessageParam +from pydantic import BaseModel, Field + + +class FormField(BaseModel): + id: str = Field(description="The unique identifier of the field.") + name: str = Field(description="The name of the field.") + description: str = Field(description="The description of the field.") + type: Literal["string", "bool", "multiple_choice"] = Field(description="The type of the field.") + options: list[str] = Field(description="The options for multiple choice fields.") + required: bool = Field(description="Whether the field is required or not.") + + +class FormFields(BaseModel): + error_message: str = Field( + description="The error message in the case that the form fields could not be extracted." + ) + fields: list[FormField] = Field(description="The fields in the form.") + + +class NoResponseChoicesError(Exception): + pass + + +class NoParsedMessageError(Exception): + pass + + +async def extract( + async_openai_client: AsyncOpenAI, openai_model: str, max_response_tokens: int, form_content: str +) -> tuple[FormFields, dict[str, Any]]: + messages: list[ChatCompletionMessageParam] = [ + { + "role": "system", + "content": ( + "Extract the form fields from the provided form attachment. Any type of form is allowed, including for example" + " tax forms, address forms, surveys, and other official or unofficial form-types. If the content is not a form," + " or the fields cannot be determined, then set the error_message." + ), + }, + { + "role": "user", + "content": form_content, + }, + ] + + async with async_openai_client as client: + response = await client.beta.chat.completions.parse( + messages=messages, + model=openai_model, + response_format=FormFields, + ) + + if not response.choices: + raise NoResponseChoicesError() + + if not response.choices[0].message.parsed: + raise NoParsedMessageError() + + metadata = { + "request": { + "model": openai_model, + "messages": openai_client.truncate_messages_for_logging(messages), + "max_tokens": max_response_tokens, + "response_format": FormFields.model_json_schema(), + }, + "response": response.model_dump(), + } + + return response.choices[0].message.parsed, metadata diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/final_update.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/final_update.py new file mode 100644 index 00000000..1d3690fe --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/final_update.py @@ -0,0 +1,87 @@ +import logging + +from form_filler_skill.artifact import Artifact +from form_filler_skill.definition import GCDefinition +from form_filler_skill.message import Conversation +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider + +logger = logging.getLogger(__name__) + + +final_update_template = """You are a helpful, thoughtful, and meticulous assistant. +You just finished a conversation with a user.{% if context %} Here is some additional context about the conversation: +{{ context }}{% endif %} + +Your goal is to complete an artifact as thoroughly and accurately as possible based on the conversation. + +This is the schema of the artifact: +{{ artifact_schema }} + +You will be given the current state of the artifact as well as the conversation history. +Note that if the value for a field in the artifact is 'Unanswered', it means that the field was not completed. \ +Some fields may have already been completed during the conversation. + +Your need to determine whether there are any fields that need to be updated, and if so, update them. +- You should only update a field if both of the following conditions are met: (a) the current state does NOT adequately reflect the conversation \ +and (b) you are able to submit a valid value for a field. \ +You are allowed to update completed fields, but you should only do so if the current state is inadequate, \ +e.g. the user corrected a mistake in their date of birth, but the artifact does not show the corrected version. \ +Remember that it's always an option to reset a field to "Unanswered" - this is often the best choice if the artifact contains incorrect information that cannot be corrected. \ +Do not submit a value that is identical to the current state of the field (e.g. if the field is already "Unanswered" and the user didn't provide any new information about it, you should not submit "Unanswered"). \ +- Make sure the value adheres to the constraints of the field as specified in the artifact schema. \ +If it's not possible to update a field with a valid value (e.g., the user provided an invalid date of birth), you should not update the field. +- If the artifact schema is open-ended (e.g. it asks you to rate how pressing the user's issue is, without specifying rules for doing so), \ +use your best judgment to determine whether you have enough information to complete the field based on the conversation. +- Prioritize accuracy over completion. You should never make up information or make assumptions in order to complete a field. \ +For example, if the field asks for a 10-digit phone number, and the user provided a 9-digit phone number, you should not add a digit to the phone number in order to complete the field. +- If the user wasn't able to provide all of the information needed to complete a field, \ +use your best judgment to determine if a partial answer is appropriate (assuming it adheres to the formatting requirements of the field). \ +For example, if the field asks for a description of symptoms along with details about when the symptoms started, but the user wasn't sure when their symptoms started, \ +it's better to record the information they do have rather than to leave the field unanswered (and to indicate that the user was unsure about the start date). +- It's possible to update multiple fields at once (assuming you're adhering to the above rules in all cases). It's also possible that no fields need to be updated. + +Your task is to state your step-by-step reasoning about what to update, followed by a final recommendation. +Someone else will be responsible for executing the updates and they will only have access to your output \ +(not any of the conversation history, artifact schema, or other context) so make sure to specify exactly which \ +fields to update and the values to update them with, or to state that no fields need to be updated. +""" + +USER_MESSAGE_TEMPLATE = """Conversation history: +{{ conversation_history }} + +Current state of the artifact: +{{ artifact_state }}""" + + +async def final_update( + open_ai_client: AsyncOpenAI | AsyncAzureOpenAI, + definition: GCDefinition, + chat_history: Conversation, + artifact: Artifact, +): + history = InMemoryMessageHistoryProvider() + + history.append_system_message( + final_update_template, + { + "context": definition.conversation_context, + "artifact_schema": artifact.get_schema_for_prompt(), + }, + ) + history.append_user_message( + USER_MESSAGE_TEMPLATE, + { + "conversation_history": str(chat_history), + "artifact_state": artifact.get_artifact_for_prompt(), + }, + ) + + config = ChatDriverConfig( + openai_client=open_ai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/fix_agenda_error.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/fix_agenda_error.py new file mode 100644 index 00000000..084762f6 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/fix_agenda_error.py @@ -0,0 +1,53 @@ +import logging + +from form_filler_skill.message import Conversation, ConversationMessageType +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider + +logger = logging.getLogger(__name__) + +AGENDA_ERROR_CORRECTION_SYSTEM_TEMPLATE = """You are a helpful, thoughtful, and meticulous assistant. +You are conducting a conversation with a user. You tried to update the agenda, but the update was invalid. + +You will be provided the history of your conversation with the user, your previous attempt(s) at updating the agenda, and the error message(s) that resulted from your attempt(s). +Your task is to correct the update so that it is valid. + +Your changes should be as minimal as possible - you are focused on fixing the error(s) that caused the update to be invalid. + +Note that if the resource allocation is invalid, you must follow these rules: + +1. You should not change the description of the first item (since it has already been executed), but you can change its resource allocation. +2. For all other items, you can combine or split them, or assign them fewer or more resources, but the content they cover collectively should not change (i.e. don't eliminate or add new topics). +For example, the invalid attempt was "item 1 = ask for date of birth (1 turn), item 2 = ask for phone number (1 turn), item 3 = ask for phone type (1 turn), item 4 = explore treatment history (6 turns)", and the error says you need to correct the total resource allocation to 7 turns. A bad solution is "item 1 = ask for date of birth (1 turn), item 2 = explore treatment history (6 turns)" because it eliminates the phone number and phone type topics. A good solution is "item 1 = ask for date of birth (2 turns), item 2 = ask for phone number, phone type, and treatment history (2 turns), item 3 = explore treatment history (3 turns)." +""" + + +async def fix_agenda_error( + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + previous_attempts: str, + conversation: Conversation, +): + history = InMemoryMessageHistoryProvider() + + history.append_system_message(AGENDA_ERROR_CORRECTION_SYSTEM_TEMPLATE) + history.append_user_message( + ( + "Conversation history:\n" + "{{ conversation_history }}\n\n" + "Previous attempts to update the agenda:\n" + "{{ previous_attempts }}" + ), + { + "conversation_history": str(conversation.exclude([ConversationMessageType.REASONING])), + "previous_attempts": previous_attempts, + }, + ) + + config = ChatDriverConfig( + openai_client=openai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/fix_artifact_error.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/fix_artifact_error.py new file mode 100644 index 00000000..0dea8d17 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/fix_artifact_error.py @@ -0,0 +1,63 @@ +import logging + +from events import BaseEvent +from form_filler_skill.message import Conversation, ConversationMessageType +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider + +logger = logging.getLogger(__name__) + +ARTIFACT_ERROR_CORRECTION_SYSTEM_TEMPLATE = """You are a helpful, thoughtful, and meticulous assistant. + +You are conducting a conversation with a user. Your goal is to complete an artifact as thoroughly as possible by the end of the conversation. + +You have tried to update a field in the artifact, but the value you provided did not adhere to the constraints of the field as specified in the artifact schema. + +You will be provided the history of your conversation with the user, the schema for the field, your previous attempt(s) at updating the field, and the error message(s) that resulted from your attempt(s). + +Your task is to return the best possible action to take next: + +1. UPDATE_FIELD(value) +- You should pick this action if you have a valid value to submit for the field in question. Replace "value" with the correct value. + +2. RESUME_CONVERSATION +- You should pick this action if: (a) you do NOT have a valid value to submit for the field in question, and (b) you need to ask the user for more information in order to obtain a valid value. For example, if the user stated that their date of birth is June 2000, but the artifact field asks for the date of birth in the format "YYYY-MM-DD", you should resume the conversation and ask the user for the day. + +Return only the action, either UPDATE_ARTIFACT(value) or RESUME_CONVERSATION, as your response. If you selected, UPDATE_ARTIFACT, make sure to replace "value" with the correct value. +""" + + +async def fix_artifact_error( + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + previous_attempts: str, + artifact_schema: str, + conversation: Conversation, + field_name: str, +) -> BaseEvent: + history = InMemoryMessageHistoryProvider() + history.append_system_message(ARTIFACT_ERROR_CORRECTION_SYSTEM_TEMPLATE) + history.append_user_message( + ( + "Conversation history:\n" + "{{ conversation_history }}\n\n" + "Schema:\n" + "{{ artifact_schema }}\n\n" + 'Previous attempts to update the field "{{ field_name }}" in the artifact:\n' + "{{ previous_attempts }}" + ), + { + "conversation_history": str(conversation.exclude([ConversationMessageType.REASONING])), + "artifact_schema": artifact_schema, + "field_name": field_name, + "previous_attempts": previous_attempts, + }, + ) + + config = ChatDriverConfig( + openai_client=openai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/generate_filled_form.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/generate_filled_form.py new file mode 100644 index 00000000..e69de29b diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/choose_action.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/choose_action.py new file mode 100644 index 00000000..e03cc6e7 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/choose_action.py @@ -0,0 +1,237 @@ +import logging + +from form_filler_skill.agenda import Agenda, AgendaItem +from form_filler_skill.definition import GCDefinition +from form_filler_skill.message import Conversation +from form_filler_skill.resources import ( + GCResource, + ResourceConstraintMode, + ResourceConstraintUnit, + format_resource, +) +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider +from pydantic import ValidationError +from skill_library.run_context import RunContext + +from ...artifact import Artifact +from ..fix_agenda_error import fix_agenda_error +from ..update_agenda_template import update_agenda_template + +logger = logging.getLogger(__name__) + + +def _get_termination_instructions(resource: GCResource): + """ + Get the termination instructions for the conversation. This is contingent on + the resources mode, if any, that is available. Assumes we're always using + turns as the resource unit. + """ + # Termination condition under no resource constraints + if resource.resource_constraint is None: + return ( + "- You should pick this action as soon as you have completed the artifact to the best of your ability, the" + " conversation has come to a natural conclusion, or the user is not cooperating so you cannot continue the" + " conversation." + ) + + # Termination condition under exact resource constraints + if resource.resource_constraint.mode == ResourceConstraintMode.EXACT: + return ( + "- You should only pick this action if the user is not cooperating so you cannot continue the conversation." + ) + + # Termination condition under maximum resource constraints + elif resource.resource_constraint.mode == ResourceConstraintMode.MAXIMUM: + return ( + "- You should pick this action as soon as you have completed the artifact to the best of your ability, the" + " conversation has come to a natural conclusion, or the user is not cooperating so you cannot continue the" + " conversation." + ) + + else: + logger.error("Invalid resource mode provided.") + return "" + + +async def update_agenda( + context: RunContext, + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + definition: GCDefinition, + chat_history: Conversation, + agenda: Agenda, + artifact: Artifact, + resource: GCResource, +) -> bool: + # STEP 1: Generate an updated agenda. + + # If there is a resource constraint and there's more than one turn left, + # include additional constraint instructions. + remaining_resource = resource.remaining_units if resource.remaining_units else 0 + resource_instructions = resource.get_resource_instructions() + if (resource_instructions != "") and (remaining_resource > 1): + match resource.get_resource_mode(): + case ResourceConstraintMode.MAXIMUM: + total_resource_str = f"does not exceed the remaining turns ({remaining_resource})." + ample_time_str = "" + case ResourceConstraintMode.EXACT: + total_resource_str = ( + f"is equal to the remaining turns ({remaining_resource}). Do not leave any turns unallocated." + ) + ample_time_str = ( + "If you have many turns remaining, instead of including wrap-up items or repeating " + "topics, you should include items that increase the breadth and/or depth of the conversation " + 'in a way that\'s directly relevant to the artifact (e.g. "collect additional details about X", ' + '"ask for clarification about Y", "explore related topic Z", etc.).' + ) + case _: + logger.error("Invalid resource mode.") + else: + total_resource_str = "" + ample_time_str = "" + + history = InMemoryMessageHistoryProvider() + history.append_system_message( + update_agenda_template, + { + "context": definition.conversation_context, + "artifact_schema": definition.artifact_schema, + "rules": definition.rules, + "current_state_description": definition.conversation_flow, + "show_agenda": True, + "remaining_resource": remaining_resource, + "total_resource_str": total_resource_str, + "ample_time_str": ample_time_str, + "termination_instructions": _get_termination_instructions(resource), + "resource_instructions": resource_instructions, + }, + ) + history.append_user_message( + ( + "Conversation history:\n" + "{{ chat_history }}\n\n" + "Latest agenda:\n" + "{{ agenda_state }}\n\n" + "Current state of the artifact:\n" + "{{ artifact_state }}" + ), + { + "chat_history": str(chat_history), + "agenda_state": get_agenda_for_prompt(agenda), + "artifact_state": artifact.get_artifact_for_prompt(), + }, + ) + + config = ChatDriverConfig( + openai_client=openai_client, + model="gpt-4o", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + response = await chat_driver.respond() + items = response.message + + # STEP 2: Validate/fix the updated agenda. + + previous_attempts = [] + while True: + try: + # Pydantic type validation. + agenda.items = items # type: ignore + + # Check resource constraints. + if agenda.resource_constraint_mode is not None: + check_item_constraints( + agenda.resource_constraint_mode, + agenda.items, + resource.estimate_remaining_turns(), + ) + + logger.info(f"Agenda updated successfully: {get_agenda_for_prompt(agenda)}") + return True + + except (ValidationError, ValueError) as e: + # If we have reached the maximum number of retries return a failure. + if len(previous_attempts) >= agenda.max_agenda_retries: + logger.warning(f"Failed to update agenda after {agenda.max_agenda_retries} attempts.") + return False + + # Otherwise, get an error string. + if isinstance(e, ValidationError): + error_str = "; ".join([e.get("msg") for e in e.errors()]) + error_str = error_str.replace("; Input should be 'Unanswered'", " or input should be 'Unanswered'") + else: + error_str = str(e) + + # Add it to our list of previous attempts. + previous_attempts.append((str(items), error_str)) + + # And try again. + logger.info(f"Attempting to fix the agenda error. Attempt {len(previous_attempts)}.") + llm_formatted_attempts = "\n".join([ + f"Attempt: {attempt}\nError: {error}" for attempt, error in previous_attempts + ]) + response = await fix_agenda_error(openai_client, llm_formatted_attempts, chat_history) + + # Now, update the items with the corrected agenda and try to + # validate again. + items = response.message + + +def check_item_constraints( + resource_constraint_mode: ResourceConstraintMode, + items: list[AgendaItem], + remaining_turns: int, +) -> None: + """ + Validates if any constraints were violated while performing the agenda + update. + """ + # The total, proposed allocation of resources. + total_resources = sum([item.resource for item in items]) + + violations = [] + # In maximum mode, the total resources should not exceed the remaining + # turns. + if (resource_constraint_mode == ResourceConstraintMode.MAXIMUM) and (total_resources > remaining_turns): + violations.append( + "The total turns allocated in the agenda " + f"must not exceed the remaining amount ({remaining_turns}); " + f"but the current total is {total_resources}." + ) + + # In exact mode if the total resources were not exactly equal to the + # remaining turns. + if (resource_constraint_mode == ResourceConstraintMode.EXACT) and (total_resources != remaining_turns): + violations.append( + "The total turns allocated in the agenda " + f"must equal the remaining amount ({remaining_turns}); " + f"but the current total is {total_resources}." + ) + + # Check if any item has a resource value of 0. + if any(item.resource <= 0 for item in items): + violations.append("All items must have a resource value greater than 0.") + + # Raise an error if any violations were found. + if len(violations) > 0: + logger.debug(f"Agenda update failed due to the following violations: {violations}.") + raise ValueError(" ".join(violations)) + + +def get_agenda_for_prompt(agenda: Agenda) -> str: + """ + Gets a string representation of the agenda for use in an LLM prompt. + """ + agenda_json = agenda.model_dump() + agenda_items = agenda_json.get("items", []) + if len(agenda_items) == 0: + return "None" + agenda_str = "\n".join([ + f"{i + 1}. [{format_resource(item['resource'], ResourceConstraintUnit.TURNS)}] {item['title']}" + for i, item in enumerate(agenda_items) + ]) + total_resource = format_resource(sum([item["resource"] for item in agenda_items]), ResourceConstraintUnit.TURNS) + agenda_str += f"\nTotal = {total_resource}" + return agenda_str diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/choose_action_template.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/choose_action_template.py new file mode 100644 index 00000000..3e533f63 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/choose_action_template.py @@ -0,0 +1,52 @@ +update_agenda_template = """You are a helpful, thoughtful, and meticulous assistant. You are conducting a conversation with a user. Your goal is to complete an artifact as thoroughly as possible by the end of the conversation, and to ensure a smooth experience for the user. + +This is the schema of the artifact you are completing: +{{ artifact_schema }}{% if context %} + +Here is some additional context about the conversation: +{{ context }}{% endif %} + +Throughout the conversation, you must abide by these rules: +{{ rules }}{% if current_state_description %} + +Here's a description of the conversation flow: +{{ current_state_description }} +Follow this description, and exercise good judgment about when it is appropriate to deviate.{% endif %} + +You will be provided the history of your conversation with the user up until now and the current state of the artifact. +Note that if the value for a field in the artifact is 'Unanswered', it means that the field has not been completed. +You need to select the best possible action(s), given the state of the conversation and the artifact. + +These are the possible actions you can take: + +{% if show_agenda %}Update agenda (required parameters: items) +- If the latest agenda is set to "None", you should always pick this action. +- You should pick this action if you need to change your plan for the conversation to make the best use of the remaining turns available to you. Consider how long it usually takes to get the information you need (which is a function of the quality and pace of the user's responses), the number, complexity, and importance of the remaining fields in the artifact, and the number of turns remaining ({{ remaining_resource }}). Based on these factors, you might need to accelerate (e.g. combine several topics) or slow down the conversation (e.g. spread out a topic), in which case you should update the agenda accordingly. Note that skipping an artifact field is NOT a valid way to accelerate the conversation. +- You must provide an ordered list of items to be completed sequentially, where the first item contains everything you will do in the current turn of the conversation (in addition to updating the agenda). For example, if you choose to send a message to the user asking for their name and medical history, then you would write "ask for name and medical history" as the first item. If you think medical history will take longer than asking for the name, then you would write "complete medical history" as the second item, with an estimate of how many turns you think it will take. Do NOT include items that have already been completed. Items must always represent a conversation topic (corresponding to the "Send message to user" action). Updating the artifact (e.g. "update field X based on the discussion") or terminating the conversation is NOT a valid item. +- The latest agenda was created in the previous turn of the conversation. Even if the total turns in the latest agenda equals the remaining turns, you should still update the agenda if you +think the current plan is suboptimal (e.g. the first item was completed, the order of items is not ideal, an item is too broad or not a conversation topic, etc.). +- Each item must have a description and and your best guess for the number of turns required to complete it. Do not provide a range of turns. It is EXTREMELY important that the total turns allocated across all items in the updated agenda (including the first item for the current turn) {{ total_resource_str }} Everything in the agenda should be something you expect to complete in the remaining turns - there shouldn't be any optional "buffer" items. It can be helpful to include the cumulative turns allocated for each item in the agenda to ensure you adhere to this rule, e.g. item 1 = 2 turns (cumulative total = 2), item 2 = 4 turns (cumulative total = 6), etc. +- Avoid high-level items like "ask follow-up questions" - be specific about what you need to do. +- Do NOT include wrap-up items such as "review and confirm all information with the user" (you should be doing this throughout the conversation) or "thank the user for their time". Do NOT repeat topics that have already been sufficiently addressed. {{ ample_time_str }}{% endif %} + +Send message to user (required parameters: message) +- If there is no conversation history, you should always pick this action. +- You should pick this action if (a) the user asked a question or made a statement that you need to respond to, or (b) you need to follow-up with the user because the information they provided is incomplete, invalid, ambiguous, or in some way insufficient to complete the artifact. For example, if the artifact schema indicates that the "date of birth" field must be in the format "YYYY-MM-DD", but the user has only provided the month and year, you should send a message to the user asking for the day. Likewise, if the user claims that their date of birth is February 30, you should send a message to the user asking for a valid date. If the artifact schema is open-ended (e.g. it asks you to rate how pressing the user's issue is, without specifying rules for doing so), use your best judgment to determine whether you have enough information or you need to continue probing the user. It's important to be thorough, but also to avoid asking the user for unnecessary information. + +Update artifact fields (required parameters: field, value) +- You should pick this action as soon as (a) the user provides new information that is not already reflected in the current state of the artifact and (b) you are able to submit a valid value for a field in the artifact using this new information. If you have already updated a field in the artifact and there is no new information to update the field with, you should not pick this action. +- Make sure the value adheres to the constraints of the field as specified in the artifact schema. +- If the user has provided all required information to complete a field (i.e. the criteria for "Send message to user" are not satisfied) but the information is in the wrong format, you should not ask the user to reformat their response. Instead, you should simply update the field with the correctly formatted value. For example, if the artifact asks for the date of birth in the format "YYYY-MM-DD", and the user provides their date of birth as "June 15, 2000", you should update the field with the value "2000-06-15". +- Prioritize accuracy over completion. You should never make up information or make assumptions in order to complete a field. For example, if the field asks for a 10-digit phone number, and the user provided a 9-digit phone number, you should not add a digit to the phone number in order to complete the field. Instead, you should follow-up with the user to ask for the correct phone number. If they still aren't able to provide one, you should leave the field unanswered. +- If the user isn't able to provide all of the information needed to complete a field, use your best judgment to determine if a partial answer is appropriate (assuming it adheres to the formatting requirements of the field). For example, if the field asks for a description of symptoms along with details about when the symptoms started, but the user isn't sure when their symptoms started, it's better to record the information they do have rather than to leave the field unanswered (and to indicate that the user was unsure about the start date). +- If it's possible to update multiple fields at once (assuming you're adhering to the above rules in all cases), you should do so. For example, if the user provides their full name and date of birth in the same message, you should select the "update artifact fields" action twice, once for each field. + +End conversation (required parameters: None) +{{ termination_instructions }} +{{ resource_instructions }} + +If you select the "Update artifact field" action or the "Update agenda" action, you should also select one of the "Send message to user" or "End conversation" actions. Note that artifact and updates updates will always be executed before a message is sent to the user or the +conversation is terminated. Also note that only one message can be sent to the user at a time. + +Your task is to state your step-by-step reasoning for the best possible action(s), followed by a final recommendation of which action(s) to take, including all required parameters. Someone else will be responsible for executing the action(s) you select and they will only have access to your output (not any of the conversation history, artifact schema, or other context) so it is EXTREMELY important that you clearly specify the value of all required parameters for each action you select. +""" diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/execute_reasoning.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/execute_reasoning.py new file mode 100644 index 00000000..c81448b7 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/unneeded/execute_reasoning.py @@ -0,0 +1,57 @@ +import logging + +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider + +logger = logging.getLogger(__name__) + +SYSTEM_TEMPLATE = """You are a helpful, thoughtful, and meticulous assistant. You are conducting a conversation with a user. Your goal is to complete an artifact as thoroughly as possible by the end of the conversation. + +You will be given some reasoning about the best possible action(s) to take next given the state of the conversation as well as the artifact schema. + +The reasoning is supposed to state the recommended action(s) to take next, along with all required parameters for each action. + +Your task is to execute ALL actions recommended in the reasoning in the order they are listed. +If the reasoning's specification of an action is incomplete (e.g. it doesn't include all required parameters for the action, or some parameters are specified implicitly, such as "send a message that contains a greeting" instead of explicitly providing the value of the "message" parameter), do not execute the action. You should never fill in missing or imprecise +parameters yourself. + +If the reasoning is not clear about which actions to take, or all actions are specified in an incomplete way, return 'None' without selecting any action.""" + +USER_TEMPLATE = """Artifact schema: +{{ artifact_schema }} + +If the type in the schema is str, the "field_value" parameter in the action should be also be a string. +These are example parameters for the update_artifact action: {"field_name": "company_name", "field_value": "Contoso"} +DO NOT write JSON in the "field_value" parameter in this case. +{"field_name": "company_name", "field_value": "{"value": "Contoso"}"} is INCORRECT. + +Reasoning: +{{ reasoning }}""" + + +async def execute_reasoning( + open_ai_client: AsyncOpenAI | AsyncAzureOpenAI, + reasoning: str, + artifact_schema: str, +): + history = InMemoryMessageHistoryProvider() + + history.append_system_message( + SYSTEM_TEMPLATE, + ) + history.append_user_message( + USER_TEMPLATE, + { + "artifact_schema": artifact_schema, + "reasoning": reasoning, + }, + ) + + config = ChatDriverConfig( + openai_client=open_ai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_agenda.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_agenda.py new file mode 100644 index 00000000..22cafa50 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_agenda.py @@ -0,0 +1,126 @@ +import logging + +from form_filler_skill.agenda import Agenda, AgendaItem +from form_filler_skill.message import Conversation +from form_filler_skill.resources import ( + GCResource, + ResourceConstraintMode, + ResourceConstraintUnit, + format_resource, +) +from openai import AsyncAzureOpenAI, AsyncOpenAI +from pydantic import ValidationError + +from .fix_agenda_error import fix_agenda_error + +logger = logging.getLogger(__name__) + + +async def update_agenda( + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + items: str, + chat_history: Conversation, + agenda: Agenda, + resource: GCResource, +) -> tuple[Agenda, bool]: + previous_attempts = [] + while True: + try: + # Pydantic type validation. + agenda.items = items # type: ignore + + # Check resource constraints. + if agenda.resource_constraint_mode is not None: + check_item_constraints( + agenda.resource_constraint_mode, + agenda.items, + resource.estimate_remaining_turns(), + ) + + logger.info(f"Agenda updated successfully: {get_agenda_for_prompt(agenda)}") + return (agenda, True) + + except (ValidationError, ValueError) as e: + # If we have reached the maximum number of retries return a failure. + if len(previous_attempts) >= agenda.max_agenda_retries: + logger.warning(f"Failed to update agenda after {agenda.max_agenda_retries} attempts.") + return (agenda, False) + + # Otherwise, get an error string. + if isinstance(e, ValidationError): + error_str = "; ".join([e.get("msg") for e in e.errors()]) + error_str = error_str.replace("; Input should be 'Unanswered'", " or input should be 'Unanswered'") + else: + error_str = str(e) + + # Add it to our list of previous attempts. + previous_attempts.append((str(items), error_str)) + + # And try again. + logger.info(f"Attempting to fix the agenda error. Attempt {len(previous_attempts)}.") + llm_formatted_attempts = "\n".join([ + f"Attempt: {attempt}\nError: {error}" for attempt, error in previous_attempts + ]) + response = await fix_agenda_error(openai_client, llm_formatted_attempts, chat_history) + + # Now, update the items with the corrected agenda and try to + # validate again. + items = response.message or "" + + +def check_item_constraints( + resource_constraint_mode: ResourceConstraintMode, + items: list[AgendaItem], + remaining_turns: int, +) -> None: + """ + Validates if any constraints were violated while performing the agenda + update. + """ + # The total, proposed allocation of resources. + total_resources = sum([item.resource for item in items]) + + violations = [] + # In maximum mode, the total resources should not exceed the remaining + # turns. + if (resource_constraint_mode == ResourceConstraintMode.MAXIMUM) and (total_resources > remaining_turns): + violations.append( + "The total turns allocated in the agenda " + f"must not exceed the remaining amount ({remaining_turns}); " + f"but the current total is {total_resources}." + ) + + # In exact mode if the total resources were not exactly equal to the + # remaining turns. + if (resource_constraint_mode == ResourceConstraintMode.EXACT) and (total_resources != remaining_turns): + violations.append( + "The total turns allocated in the agenda " + f"must equal the remaining amount ({remaining_turns}); " + f"but the current total is {total_resources}." + ) + + # Check if any item has a resource value of 0. + if any(item.resource <= 0 for item in items): + violations.append("All items must have a resource value greater than 0.") + + # Raise an error if any violations were found. + if len(violations) > 0: + logger.debug(f"Agenda update failed due to the following violations: {violations}.") + raise ValueError(" ".join(violations)) + + +def get_agenda_for_prompt(agenda: Agenda) -> str: + """ + Gets a string representation of the agenda for use in an LLM prompt. + """ + agenda_json = agenda.model_dump() + agenda_items = agenda_json.get("items", []) + if len(agenda_items) == 0: + return "None" + agenda_str = "\n".join([ + f"{i + 1}. [{format_resource(item['resource'], ResourceConstraintUnit.TURNS)}] {item['title']}" + for i, item in enumerate(agenda_items) + ]) + total_resource = format_resource(sum([item["resource"] for item in agenda_items]), ResourceConstraintUnit.TURNS) + agenda_str += f"\nTotal = {total_resource}" + return agenda_str diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_agenda_template.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_agenda_template.py new file mode 100644 index 00000000..5bf468ce --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_agenda_template.py @@ -0,0 +1,31 @@ +update_agenda_template = """You are a helpful, thoughtful, and meticulous assistant. You are conducting a conversation with a user. Your goal is to complete an artifact as thoroughly as possible by the end of the conversation, and to ensure a smooth experience for the user. + +This is the schema of the artifact you are completing: +{{ artifact_schema }}{% if context %} + +Here is some additional context about the conversation: +{{ context }}{% endif %} + +Throughout the conversation, you must abide by these rules: +{{ rules }}{% if current_state_description %} + +Here's a description of the conversation flow: +{{ current_state_description }} +Follow this description, and exercise good judgment about when it is appropriate to deviate.{% endif %} + +You will be provided the history of your conversation with the user up until now and the current state of the artifact. +Note that if the value for a field in the artifact is 'Unanswered', it means that the field has not been completed. +You need to select the best possible action(s), given the state of the conversation and the artifact. + +Here is the action to take: + +- If the latest agenda is set to "None", you should always pick this action. +- You should pick this action if you need to change your plan for the conversation to make the best use of the remaining turns available to you. Consider how long it usually takes to get the information you need (which is a function of the quality and pace of the user's responses), the number, complexity, and importance of the remaining fields in the artifact, and the number of turns remaining ({{ remaining_resource }}). Based on these factors, you might need to accelerate (e.g. combine several topics) or slow down the conversation (e.g. spread out a topic), in which case you should update the agenda accordingly. Note that skipping an artifact field is NOT a valid way to accelerate the conversation. +- You must provide an ordered list of items to be completed sequentially, where the first item contains everything you will do in the current turn of the conversation (in addition to updating the agenda). For example, if you choose to send a message to the user asking for their name and medical history, then you would write "ask for name and medical history" as the first item. If you think medical history will take longer than asking for the name, then you would write "complete medical history" as the second item, with an estimate of how many turns you think it will take. Do NOT include items that have already been completed. Items must always represent a conversation topic (corresponding to the "Send message to user" action). Updating the artifact (e.g. "update field X based on the discussion") or terminating the conversation is NOT a valid item. +- The latest agenda was created in the previous turn of the conversation. Even if the total turns in the latest agenda equals the remaining turns, you should still update the agenda if you +think the current plan is suboptimal (e.g. the first item was completed, the order of items is not ideal, an item is too broad or not a conversation topic, etc.). +- Each item must have a description and and your best guess for the number of turns required to complete it. Do not provide a range of turns. It is EXTREMELY important that the total turns allocated across all items in the updated agenda (including the first item for the current turn) {{ total_resource_str }} Everything in the agenda should be something you expect to complete in the remaining turns - there shouldn't be any optional "buffer" items. It can be helpful to include the cumulative turns allocated for each item in the agenda to ensure you adhere to this rule, e.g. item 1 = 2 turns (cumulative total = 2), item 2 = 4 turns (cumulative total = 6), etc. +- Avoid high-level items like "ask follow-up questions" - be specific about what you need to do. +- Do NOT include wrap-up items such as "review and confirm all information with the user" (you should be doing this throughout the conversation) or "thank the user for their time". Do NOT repeat topics that have already been sufficiently addressed. {{ ample_time_str }}{% endif %} + +""" diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_artifact.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_artifact.py new file mode 100644 index 00000000..8f1051ec --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_artifact.py @@ -0,0 +1,204 @@ +import logging + +from form_filler_skill.agenda import Agenda, AgendaItem +from form_filler_skill.definition import GCDefinition +from form_filler_skill.message import Conversation +from form_filler_skill.resources import ( + GCResource, + ResourceConstraintMode, + ResourceConstraintUnit, + format_resource, +) +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider +from pydantic import ValidationError + +from ..artifact import Artifact +from .fix_agenda_error import fix_agenda_error +from .update_artifact_template import update_artifact_template + +logger = logging.getLogger(__name__) + + +def _get_termination_instructions(resource: GCResource): + """ + Get the termination instructions for the conversation. This is contingent on + the resources mode, if any, that is available. Assumes we're always using + turns as the resource unit. + """ + # Termination condition under no resource constraints + if resource.resource_constraint is None: + return ( + "- You should pick this action as soon as you have completed the artifact to the best of your ability, the" + " conversation has come to a natural conclusion, or the user is not cooperating so you cannot continue the" + " conversation." + ) + + # Termination condition under exact resource constraints + if resource.resource_constraint.mode == ResourceConstraintMode.EXACT: + return ( + "- You should only pick this action if the user is not cooperating so you cannot continue the conversation." + ) + + # Termination condition under maximum resource constraints + elif resource.resource_constraint.mode == ResourceConstraintMode.MAXIMUM: + return ( + "- You should pick this action as soon as you have completed the artifact to the best of your ability, the" + " conversation has come to a natural conclusion, or the user is not cooperating so you cannot continue the" + " conversation." + ) + + else: + logger.error("Invalid resource mode provided.") + return "" + + +async def update_agenda( + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + definition: GCDefinition, + chat_history: Conversation, + agenda: Agenda, + artifact: Artifact, + resource: GCResource, +) -> bool: + # STEP 1: Generate an updated agenda. + + history = InMemoryMessageHistoryProvider() + history.append_system_message( + update_artifact_template, + { + "context": definition.conversation_context, + "artifact_schema": definition.artifact_schema, + "rules": definition.rules, + "current_state_description": definition.conversation_flow, + }, + ) + history.append_user_message( + ( + "Conversation history:\n" + "{{ chat_history }}\n\n" + "Latest agenda:\n" + "{{ agenda_state }}\n\n" + "Current state of the artifact:\n" + "{{ artifact_state }}" + ), + { + "chat_history": str(chat_history), + "agenda_state": get_agenda_for_prompt(agenda), + "artifact_state": artifact.get_artifact_for_prompt(), + }, + ) + + config = ChatDriverConfig( + openai_client=openai_client, + model="gpt-4o", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + response = await chat_driver.respond() + items = response.message + + # STEP 2: Validate/fix the updated agenda. + + previous_attempts = [] + while True: + try: + # Pydantic type validation. + agenda.items = items # type: ignore + + # Check resource constraints. + if agenda.resource_constraint_mode is not None: + check_item_constraints( + agenda.resource_constraint_mode, + agenda.items, + resource.estimate_remaining_turns(), + ) + + logger.info(f"Agenda updated successfully: {get_agenda_for_prompt(agenda)}") + return True + + except (ValidationError, ValueError) as e: + # If we have reached the maximum number of retries return a failure. + if len(previous_attempts) >= agenda.max_agenda_retries: + logger.warning(f"Failed to update agenda after {agenda.max_agenda_retries} attempts.") + return False + + # Otherwise, get an error string. + if isinstance(e, ValidationError): + error_str = "; ".join([e.get("msg") for e in e.errors()]) + error_str = error_str.replace("; Input should be 'Unanswered'", " or input should be 'Unanswered'") + else: + error_str = str(e) + + # Add it to our list of previous attempts. + previous_attempts.append((str(items), error_str)) + + # And try again. + logger.info(f"Attempting to fix the agenda error. Attempt {len(previous_attempts)}.") + llm_formatted_attempts = "\n".join([ + f"Attempt: {attempt}\nError: {error}" for attempt, error in previous_attempts + ]) + response = await fix_agenda_error(openai_client, llm_formatted_attempts, chat_history) + + # Now, update the items with the corrected agenda and try to + # validate again. + items = response.message + + +def check_item_constraints( + resource_constraint_mode: ResourceConstraintMode, + items: list[AgendaItem], + remaining_turns: int, +) -> None: + """ + Validates if any constraints were violated while performing the agenda + update. + """ + # The total, proposed allocation of resources. + total_resources = sum([item.resource for item in items]) + + violations = [] + # In maximum mode, the total resources should not exceed the remaining + # turns. + if (resource_constraint_mode == ResourceConstraintMode.MAXIMUM) and (total_resources > remaining_turns): + violations.append( + "The total turns allocated in the agenda " + f"must not exceed the remaining amount ({remaining_turns}); " + f"but the current total is {total_resources}." + ) + + # In exact mode if the total resources were not exactly equal to the + # remaining turns. + if (resource_constraint_mode == ResourceConstraintMode.EXACT) and (total_resources != remaining_turns): + violations.append( + "The total turns allocated in the agenda " + f"must equal the remaining amount ({remaining_turns}); " + f"but the current total is {total_resources}." + ) + + # Check if any item has a resource value of 0. + if any(item.resource <= 0 for item in items): + violations.append("All items must have a resource value greater than 0.") + + # Raise an error if any violations were found. + if len(violations) > 0: + logger.debug(f"Agenda update failed due to the following violations: {violations}.") + raise ValueError(" ".join(violations)) + + +def get_agenda_for_prompt(agenda: Agenda) -> str: + """ + Gets a string representation of the agenda for use in an LLM prompt. + """ + agenda_json = agenda.model_dump() + agenda_items = agenda_json.get("items", []) + if len(agenda_items) == 0: + return "None" + agenda_str = "\n".join([ + f"{i + 1}. [{format_resource(item['resource'], ResourceConstraintUnit.TURNS)}] {item['title']}" + for i, item in enumerate(agenda_items) + ]) + total_resource = format_resource(sum([item["resource"] for item in agenda_items]), ResourceConstraintUnit.TURNS) + agenda_str += f"\nTotal = {total_resource}" + return agenda_str diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_artifact_template.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_artifact_template.py new file mode 100644 index 00000000..33d8e454 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_artifact_template.py @@ -0,0 +1,32 @@ +update_artifact_template = """You are a helpful, thoughtful, and meticulous assistant. You are conducting a conversation with a user. Your goal is to complete an artifact as thoroughly as possible by the end of the conversation, and to ensure a smooth experience for the user. + +This is the schema of the artifact you are completing: +{{ artifact_schema }}{% if context %} + +Here is some additional context about the conversation: +{{ context }}{% endif %} + +Throughout the conversation, you must abide by these rules: +{{ rules }}{% if current_state_description %} + +Here's a description of the conversation flow: +{{ current_state_description }} +Follow this description, and exercise good judgment about when it is appropriate to deviate.{% endif %} + +You will be provided the history of your conversation with the user up until now and the current state of the artifact. +Note that if the value for a field in the artifact is 'Unanswered', it means that the field has not been completed. +You need to select the best possible action(s), given the state of the conversation and the artifact. + +Your job is to create a list of field updates to update the artifact. Each update should be listed as: + +update_artifact_field(required parameters: field, value) + +- You should pick this action as soon as (a) the user provides new information that is not already reflected in the current state of the artifact and (b) you are able to submit a valid value for a field in the artifact using this new information. If you have already updated a field in the artifact and there is no new information to update the field with, you should not pick this action. +- Make sure the value adheres to the constraints of the field as specified in the artifact schema. +- If the user has provided all required information to complete a field (i.e. the criteria for "Send message to user" are not satisfied) but the information is in the wrong format, you should not ask the user to reformat their response. Instead, you should simply update the field with the correctly formatted value. For example, if the artifact asks for the date of birth in the format "YYYY-MM-DD", and the user provides their date of birth as "June 15, 2000", you should update the field with the value "2000-06-15". +- Prioritize accuracy over completion. You should never make up information or make assumptions in order to complete a field. For example, if the field asks for a 10-digit phone number, and the user provided a 9-digit phone number, you should not add a digit to the phone number in order to complete the field. Instead, you should follow-up with the user to ask for the correct phone number. If they still aren't able to provide one, you should leave the field unanswered. +- If the user isn't able to provide all of the information needed to complete a field, use your best judgment to determine if a partial answer is appropriate (assuming it adheres to the formatting requirements of the field). For example, if the field asks for a description of symptoms along with details about when the symptoms started, but the user isn't sure when their symptoms started, it's better to record the information they do have rather than to leave the field unanswered (and to indicate that the user was unsure about the start date). +- If it's possible to update multiple fields at once (assuming you're adhering to the above rules in all cases), you should do so. For example, if the user provides their full name and date of birth in the same message, you should select the "update artifact fields" action twice, once for each field. + +Your task is to state your step-by-step reasoning for the best possible action(s), followed by a final recommendation of which update(s) to make, including all required parameters. Someone else will be responsible for executing the update(s) you select and they will only have access to your output (not any of the conversation history, artifact schema, or other context) so it is EXTREMELY important that you clearly specify the value of all required parameters for each update you make. +""" diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_form.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/chat_drivers/update_form.py new file mode 100644 index 00000000..e69de29b diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/definition.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/definition.py new file mode 100644 index 00000000..b2480218 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/definition.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from typing import Optional + +from form_filler_skill.resources import ResourceConstraint + + +@dataclass +class GCDefinition: + artifact_schema: str + rules: str + conversation_flow: Optional[str] + conversation_context: str + resource_constraint: Optional[ResourceConstraint] + service_id: str = "gc_main" diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/form_filler_skill.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/form_filler_skill.py new file mode 100644 index 00000000..1e901c18 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/form_filler_skill.py @@ -0,0 +1,133 @@ +# flake8: noqa +# ruff: noqa + +from typing import Any, Optional + +from openai_client.chat_driver import ChatDriverConfig +from skill_library import FunctionRoutine, RoutineTypes, Skill +from skill_library.run_context import RunContext + +NAME = "form-filler" +CLASS_NAME = "FormFillerSkill" +DESCRIPTION = "Walks the user through uploading and filling out a form." +DEFAULT_MAX_RETRIES = 3 +INSTRUCTIONS = "You are an assistant." + + +class FormFillerSkill(Skill): + def __init__( + self, + chat_driver_config: ChatDriverConfig, + ) -> None: + # Put all functions in a group. We are going to use all these as (1) + # skill actions, but also as (2) chat functions and (3) chat commands. + # You can also put them in separate lists if you want to differentiate + # between these. + functions = [] + + # Add some skill routines. + routines: list[RoutineTypes] = [ + self.form_filler_routine(), + ] + + # Re-configure the skill's chat driver. + chat_driver_config.instructions = INSTRUCTIONS + chat_driver_config.commands = functions + chat_driver_config.functions = functions + + # TODO: change where this is from. + self.openai_client = chat_driver_config.openai_client + + # Initialize the skill! + super().__init__( + name=NAME, + description=DESCRIPTION, + chat_driver_config=chat_driver_config, + skill_actions=functions, + routines=routines, + ) + + ################################## + # Routines + ################################## + + def form_filler_routine(self) -> FunctionRoutine: + return FunctionRoutine( + name="form_filler", + description="Run a form-filler routine.", + init_function=self.form_fill_init, + step_function=self.form_fill_step, + skill=self, + ) + + async def form_fill_init(self, context: RunContext, vars: dict[str, Any] | None = None): + # TODO: Use `vars` to config the form filler routine. + await self.form_fill_step(context) + + async def form_fill_step( + self, + context: RunContext, + message: Optional[str] = None, + ) -> str | None: + FormFiller = self + state = await context.state() + while True: + match state.get("mode"): + case None: + state["mode"] = "init" + case "init": + # Cede control to guided conversation if an artifact needs to be generated. + # How do we want to pass in all the GC definitions? Should they just be a simpler config object TYPE? + if not state["artifact"]: + if not state["gce_id"]: + guided_conversation_vars: dict[str, Any] = { + "definition_type": "upload_files", + "objective": "Upload a form to be filled out by the form filler recipe.", + } + gc_id = await context.run_routine( + context, "guided_conversation.doc_upload", guided_conversation_vars + ) + state["gc_id"] = gc_id + # FIXME: This is not implemented yet. + # artifact = GuidedConversation.run(state["gce_id"], message) + # if artifact: + # state["artifact"] = artifact + # else: + # await context.update_state(state) + # return + + agenda, is_done = FormFiller.update_agenda(context) + state["agenda"] = agenda + if is_done: + state["mode"] = "done" + state["mode"] = "conversation" + await context.update_state(state) + return agenda + case "conversation": + state["form"] = FormFiller.update_form(context) + agenda, is_done = FormFiller.update_agenda(context) + state["agenda"] = agenda + if is_done: + state["mode"] = "finalize" + await context.update_state(state) + return agenda + case "finalize": + message = FormFiller.generate_filled_form(context) + state["mode"] = "done" + await context.update_state(state) + return message + case "done": + return None + + ################################## + # Actions + ################################## + + def update_agenda(self, context: RunContext): + return "message", False + + def update_form(self, context: RunContext): + return "message", False + + def generate_filled_form(self, context: RunContext): + return "message" diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/__init__.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/agenda.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/agenda.py new file mode 100644 index 00000000..9d5623e9 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/agenda.py @@ -0,0 +1,24 @@ +import logging + +from pydantic import Field + +from form_filler_skill.base_model_llm import BaseModelLLM +from form_filler_skill.resources import ( + ResourceConstraintMode, +) + +logger = logging.getLogger(__name__) + + +class AgendaItem(BaseModelLLM): + title: str = Field(description="Brief description of the item") + resource: int = Field(description="Number of turns required for the item") + + +class Agenda(BaseModelLLM): + resource_constraint_mode: ResourceConstraintMode | None = Field(default=None) + max_agenda_retries: int = Field(default=2) + items: list[AgendaItem] = Field( + description="Ordered list of items to be completed in the remainder of the conversation", + default_factory=list, + ) diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/artifact.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/artifact.py new file mode 100644 index 00000000..b4f29717 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/artifact.py @@ -0,0 +1,479 @@ +# Copyright (c) Microsoft. All rights reserved. + +# FIXME: Copied code from Semantic Kernel repo, using as-is despite type errors +# type: ignore + +import logging +from typing import Annotated, Any, Literal, get_args, get_origin, get_type_hints + +from guided_conversation.utils.base_model_llm import BaseModelLLM +from guided_conversation.utils.conversation_helpers import Conversation, ConversationMessageType +from guided_conversation.utils.openai_tool_calling import ToolValidationResult +from guided_conversation.utils.plugin_helpers import PluginOutput, fix_error, update_attempts +from pydantic import BaseModel, create_model +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior +from semantic_kernel.contents import AuthorRole, ChatMessageContent +from semantic_kernel.functions import KernelArguments +from semantic_kernel.functions.kernel_function_decorator import kernel_function + +ARTIFACT_ERROR_CORRECTION_SYSTEM_TEMPLATE = """You are a helpful, thoughtful, and meticulous assistant. +You are conducting a conversation with a user. Your goal is to complete an artifact as thoroughly as possible by the end of the conversation. +You have tried to update a field in the artifact, but the value you provided did not adhere \ +to the constraints of the field as specified in the artifact schema. +You will be provided the history of your conversation with the user, the schema for the field, \ +your previous attempt(s) at updating the field, and the error message(s) that resulted from your attempt(s). +Your task is to select the best possible action to take next: +1. Update artifact +- You should pick this action if you have a valid value to submit for the field in question. +2. Resume conversation +- You should pick this action if: (a) you do NOT have a valid value to submit for the field in question, and \ +(b) you need to ask the user for more information in order to obtain a valid value. \ +For example, if the user stated that their date of birth is June 2000, but the artifact field asks for the date of birth in the format \ +"YYYY-MM-DD", you should resume the conversation and ask the user for the day. + +Conversation history: +{{ conversation_history }} + +Schema: +{{ artifact_schema }} + +Previous attempts to update the field "{{ field_name }}" in the artifact: +{{ previous_attempts }}""" + +UPDATE_ARTIFACT_TOOL = "update_artifact_field" +RESUME_CONV_TOOL = "resume_conversation" + + +class Artifact: + """The Artifact plugin takes in a Pydantic base model, and robustly handles updating the fields of the model + A typical use case is as a form an agent must complete throughout a conversation. + Another use case is as a working memory for the agent. + + The primary interface is update_artifact, which takes in the field_name to update and its new value. + Additionally, the chat_history is passed in to help the agent make informed decisions in case an error occurs. + + The Artifact also exposes several functions to access internal state: + get_artifact_for_prompt, get_schema_for_prompt, and get_failed_fields. + """ + + def __init__(self, service_id: str, input_artifact: BaseModel, max_artifact_field_retries: int = 2) -> None: + """ + Initialize the Artifact plugin with the given Pydantic base model. + + Args: + kernel (Kernel): The Semantic Kernel instance to use for calling the LLM. Don't forget to set your + req_settings since this class uses tool calling functionality from the Semantic Kernel. + service_id (str): The service ID to use for the Semantic Kernel tool calling. One kernel can have multiple + services. The service ID is used to identify which service to use for LLM calls. The Artifact object + assumes that the service has tool calling capabilities and is some flavor of chat completion. + input_artifact (BaseModel): The Pydantic base model to use as the artifact + max_artifact_field_retries (int): The maximum number of times to retry updating a field in the artifact + """ + logger = logging.getLogger(__name__) + self.logger = logger + + self.id = "artifact_plugin" + self.service_id = service_id + self.max_artifact_field_retries = max_artifact_field_retries + + self.original_schema = input_artifact.model_json_schema() + self.artifact = self._initialize_artifact(input_artifact) + + # failed_artifact_fields maps a field name to a list of the history of the failed attempts to update it + # dict: key = field, value = list of tuple[attempt, error message] + self.failed_artifact_fields: dict[str, list[tuple[str, str]]] = {} + + # The following are the kernel functions that will be provided to the LLM call + @kernel_function( + name=UPDATE_ARTIFACT_TOOL, + description="Sets the value of a field in the artifact", + ) + def update_artifact_field( + self, + field: Annotated[str, "The name of the field to update in the artifact"], + value: Annotated[str, "The value to set the field to"], + ) -> None: + pass + + @kernel_function( + name=RESUME_CONV_TOOL, + description="Resumes conversation to get more information from the user ", + ) + def resume_conversation(self): + pass + + async def update_artifact(self, field_name: str, field_value: Any, conversation: Conversation) -> PluginOutput: + """The core interface for the Artifact plugin. + This function will attempt to update the given field_name to the given field_value. + If the field_value fails Pydantic validation, an LLM will determine one of two actions to take. + Given the conversation as additional context the two actions are: + - Retry the update the artifact by fixing the formatting using the previous failed attempts as guidance + - Take no action or in other words, resume the conversation to ask the user for more information because the user gave incomplete or incorrect information + + Args: + field_name (str): The name of the field to update in the artifact + field_value (Any): The value to set the field to + conversation (Conversation): The conversation object that contains the history of the conversation + + Returns: + PluginOutput: An object with two fields: a boolean indicating success + and a list of conversation messages that may have been generated. + + Several outcomes can happen: + - The update may have failed due to + - A field_name that is not valid in the artifact. + - The field_value failing Pydantic validation and all retries failed. + - The model failed to correctly call a tool. + In this case, the boolean will be False and the list may contain a message indicating the failure. + + - The agent may have successfully updated the artifact or fixed it. + In this case, the boolean will be True and the list will contain a message indicating the update and possibly intermediate messages. + + - The agent may have decided to resume the conversation. + In this case, the boolean will be True and the messages may only contain messages indicated previous errors. + """ + + conversation_messages: list[ChatMessageContent] = [] + + # Check if the field name is valid, and return with a failure message if not + is_valid_field, msg = self._is_valid_field(field_name) + if not is_valid_field: + conversation_messages.append(msg) + return PluginOutput(update_successful=False, messages=conversation_messages) + + # Try to update the field, and handle any errors that occur until the field is + # successfully updated or skipped according to max_artifact_field_retries + while True: + try: + # Check if there have been too many previous failed attempts to update the field + if len(self.failed_artifact_fields.get(field_name, [])) >= self.max_artifact_field_retries: + self.logger.warning(f"Updating field {field_name} has failed too many times. Skipping.") + return PluginOutput(False, conversation_messages) + + # Attempt to update the artifact + msg = self._execute_update_artifact(field_name, field_value) + conversation_messages.append(msg) + return PluginOutput(True, conversation_messages) + except Exception as e: + self.logger.warning(f"Error updating field {field_name}: {e}. Retrying...") + # Handle update error will increment failed_artifact_fields, once it has failed + # greater than self.max_artifact_field_retries the field will be skipped and the loop will break + success, new_field_value = await self._handle_update_error(field_name, field_value, conversation, e) + + # The agent has successfully fixed the field. + if success and new_field_value is not None: + self.logger.info(f"Agent successfully fixed field {field_name}. New value: {new_field_value}") + field_value = new_field_value + # This is the case where the agent has decided to resume the conversation. + elif success: + self.logger.info( + f"Agent could not fix the field itself & decided to resume conversation to fix field {field_name}" + ) + return PluginOutput(True, conversation_messages) + self.logger.warning(f"Agent failed to fix field {field_name}. Retrying...") + # Otherwise, the agent has failed and we will go through the loop again + + def get_artifact_for_prompt(self) -> str: + """Returns a formatted JSON-like representation of the current state of the fields artifact. + Any fields that were failed are completely omitted. + + Returns: + str: The string representation of the artifact. + """ + failed_fields = self.get_failed_fields() + return {k: v for k, v in self.artifact.model_dump().items() if k not in failed_fields} + + def get_schema_for_prompt(self, filter_one_field: str | None = None) -> str: + """Gets a clean version of the original artifact schema, optimized for use in an LLM prompt. + + Args: + filter_one_field (str | None): If this is provided, only the schema for this one field will be returned. + + Returns: + str: The cleaned schema + """ + + def _clean_properties(schema: dict, failed_fields: list[str]) -> str: + properties = schema.get("properties", {}) + clean_properties = {} + for name, property_dict in properties.items(): + if name not in failed_fields: + cleaned_property = {} + for k, v in property_dict.items(): + if k in ["title", "default"]: + continue + cleaned_property[k] = v + clean_properties[name] = cleaned_property + + clean_properties_str = str(clean_properties) + clean_properties_str = clean_properties_str.replace("$ref", "type") + clean_properties_str = clean_properties_str.replace("#/$defs/", "") + return clean_properties_str + + # If filter_one_field is provided, only get the schema for that one field + if filter_one_field: + if not self._is_valid_field(filter_one_field): + self.logger.error(f'Field "{filter_one_field}" is not a valid field in the artifact.') + raise ValueError(f'Field "{filter_one_field}" is not a valid field in the artifact.') + filtered_schema = {"properties": {filter_one_field: self.original_schema["properties"][filter_one_field]}} + filtered_schema.update((k, v) for k, v in self.original_schema.items() if k != "properties") + schema = filtered_schema + else: + schema = self.original_schema + + failed_fields = self.get_failed_fields() + properties = _clean_properties(schema, failed_fields) + if not properties: + self.logger.error("No properties found in the schema.") + raise ValueError("No properties found in the schema.") + + types_schema = schema.get("$defs", {}) + custom_types = [] + for type_name, type_info in types_schema.items(): + if f"'type': '{type_name}'" in properties: + clean_schema = _clean_properties(type_info, []) + if clean_schema != "{}": + custom_types.append(f"{type_name} = {clean_schema}") + + if custom_types: + explanation = f"If you wanted to create a {type_name} object, for example, you would make a JSON object \ +with the following keys: {', '.join(types_schema[type_name]['properties'].keys())}." + custom_types_str = "\n".join(custom_types) + return f"""{properties} + +Here are the definitions for the custom types referenced in the artifact schema: +{custom_types_str} + +{explanation} +Remember that when updating the artifact, the field will be the original field name in the artifact and the JSON object(s) will be the value.""" + else: + return properties + + def get_failed_fields(self) -> list[str]: + """Get a list of fields that have failed all attempts to update. + + Returns: + list[str]: A list of field names that have failed all attempts to update. + """ + fields = [] + for field, attempts in self.failed_artifact_fields.items(): + if len(attempts) >= self.max_artifact_field_retries: + fields.append(field) + return fields + + def _initialize_artifact(self, artifact_model: BaseModel) -> BaseModelLLM: + """Create a new artifact model based on the one provided by the user + with "Unanswered" set for all fields. + + Args: + artifact_model (BaseModel): The Pydantic class provided by the user + + Returns: + BaseModelLLM: The new artifact model with "Unanswered" set for all fields + """ + modified_classes = self._modify_classes(artifact_model) + artifact = self._modify_base_artifact(artifact_model, modified_classes) + return artifact() + + def _get_type_if_subtype(self, target_type: type[Any], base_type: type[Any]) -> type[Any] | None: + """Recursively checks the target_type to see if it is a subclass of base_type or a generic including base_type. + + Args: + target_type: The type to check. + base_type: The type to check against. + + Returns: + The class type if target_type is base_type, a subclass of base_type, or a generic including base_type; otherwise, None. + """ + origin = get_origin(target_type) + if origin is None: + if issubclass(target_type, base_type): + return target_type + else: + # Recursively check if any of the arguments are the target type + for arg in get_args(target_type): + result = self._get_type_if_subtype(arg, base_type) + if result is not None: + return result + return None + + def _modify_classes(self, artifact_class: BaseModel) -> dict[str, type[BaseModelLLM]]: + """Find all classes used as type hints in the artifact, and modify them to set 'Unanswered' as a default and valid value for all fields.""" + modified_classes = {} + # Find any instances of BaseModel in the artifact class in the first "level" of type hints + for field_name, field_type in get_type_hints(artifact_class).items(): + is_base_model = self._get_type_if_subtype(field_type, BaseModel) + if is_base_model is not None: + modified_classes[field_name] = self._modify_base_artifact(is_base_model) + + return modified_classes + + def _replace_type_annotations( + self, field_annotation: type[Any] | None, modified_classes: dict[str, type[BaseModelLLM]] + ) -> type: + """Recursively replace type annotations with modified classes where applicable.""" + # Get the origin of the field annotation, which is the base type for generic types (e.g., List[str] -> list, Dict[str, int] -> dict) + origin = get_origin(field_annotation) + # Get the type arguments of the generic type (e.g., List[str] -> str, Dict[str, int] -> str, int) + args = get_args(field_annotation) + + if origin is None: + # The type is not generic; check if it's a subclass that needs to be replaced + if isinstance(field_annotation, type) and issubclass(field_annotation, BaseModelLLM): + return modified_classes.get(field_annotation.__name__, field_annotation) + return field_annotation + else: + # The type is generic; recursively replace the type annotations of the arguments + new_args = tuple(self._replace_type_annotations(arg, modified_classes) for arg in args) + return origin[new_args] + + def _modify_base_artifact( + self, artifact_model: type[BaseModelLLM], modified_classes: dict[str, type[BaseModelLLM]] | None = None + ) -> type[BaseModelLLM]: + """Create a new artifact model with 'Unanswered' as a default and valid value for all fields.""" + for _, field_info in artifact_model.model_fields.items(): + # Replace original classes with modified version + if modified_classes is not None: + field_info.annotation = self._replace_type_annotations(field_info.annotation, modified_classes) + # This makes it possible to always set a field to "Unanswered" + field_info.annotation = field_info.annotation | Literal["Unanswered"] + # This sets the default value to "Unanswered" + field_info.default = "Unanswered" + # This adds "Unanswered" as a possible value to any regex patterns + metadata = field_info.metadata + for m in metadata: + if hasattr(m, "pattern"): + m.pattern += "|Unanswered" + field_definitions = { + name: (field_info.annotation, field_info) for name, field_info in artifact_model.model_fields.items() + } + artifact_model = create_model("Artifact", __base__=BaseModelLLM, **field_definitions) + return artifact_model + + def _is_valid_field(self, field_name: str) -> tuple[bool, ChatMessageContent]: + """Check if the field_name is a valid field in the artifact. Returns True if it is, False and an error message otherwise.""" + if field_name not in self.artifact.model_fields: + error_message = f'Field "{field_name}" is not a valid field in the artifact.' + msg = ChatMessageContent( + role=AuthorRole.ASSISTANT, + content=error_message, + metadata={"type": ConversationMessageType.ARTIFACT_UPDATE, "turn_number": None}, + ) + return False, msg + return True, None + + async def _fix_artifact_error( + self, + field_name: str, + previous_attempts: str, + conversation_repr: str, + artifact_schema_repr: str, + ) -> dict[str, Any]: + """Calls the LLM to fix an error in the artifact using Semantic Kernel kernel.""" + + req_settings = self.kernel.get_prompt_execution_settings_from_service_id(self.service_id) + req_settings.max_tokens = 2000 + + self.kernel.add_function(plugin_name=self.id, function=self.update_artifact_field) + self.kernel.add_function(plugin_name=self.id, function=self.resume_conversation) + filter = {"included_plugins": [self.id]} + req_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(auto_invoke=False, filters=filter) + + arguments = KernelArguments( + field_name=field_name, + conversation_history=conversation_repr, + previous_attempts=previous_attempts, + artifact_schema=artifact_schema_repr, + settings=req_settings, + ) + + return await fix_error( + kernel=self.kernel, + prompt_template=ARTIFACT_ERROR_CORRECTION_SYSTEM_TEMPLATE, + req_settings=req_settings, + arguments=arguments, + ) + + def _execute_update_artifact( + self, + field_name: Annotated[str, "The name of the field to update in the artifact"], + field_value: Annotated[Any, "The value to set the field to"], + ) -> ChatMessageContent: + """Update a field in the artifact with a new value. This will raise an error if the field_value is invalid.""" + setattr(self.artifact, field_name, field_value) + msg = ChatMessageContent( + role=AuthorRole.ASSISTANT, + content=f"Assistant updated {field_name} to {field_value}", + metadata={"type": ConversationMessageType.ARTIFACT_UPDATE, "turn_number": None}, + ) + return msg + + async def _handle_update_error( + self, field_name: str, field_value: Any, conversation: Conversation, error: Exception + ) -> tuple[bool, Any]: + """ + Handles the logic for when an error occurs while updating a field. + Creates the appropriate context for the model and calls the LLM to fix the error. + + Args: + field_name (str): The name of the field to update in the artifact + field_value (Any): The value to set the field to + conversation (Conversation): The conversation object that contains the history of the conversation + error (Exception): The error that occurred while updating the field + + Returns: + tuple[bool, Any]: A tuple containing a boolean indicating success and the new field value if successful (if not, then None) + """ + # Update the failed attempts for the field + previous_attempts = self.failed_artifact_fields.get(field_name, []) + previous_attempts, llm_formatted_attempts = update_attempts( + error=error, attempt_id=str(field_value), previous_attempts=previous_attempts + ) + self.failed_artifact_fields[field_name] = previous_attempts + + # Call the LLM to fix the error + conversation_history_repr = conversation.get_repr_for_prompt(exclude_types=[ConversationMessageType.REASONING]) + artifact_schema_repr = self.get_schema_for_prompt(filter_one_field=field_name) + result = await self._fix_artifact_error( + field_name, llm_formatted_attempts, conversation_history_repr, artifact_schema_repr + ) + + # Handling the result of the LLM call + if result["validation_result"] != ToolValidationResult.SUCCESS: + return False, None + # Only consider the first tool call + tool_name = result["tool_names"][0] + tool_args = result["tool_args_list"][0] + if tool_name == f"{self.id}-{UPDATE_ARTIFACT_TOOL}": + field_value = tool_args["value"] + return True, field_value + elif tool_name == f"{self.id}-{RESUME_CONV_TOOL}": + return True, None + + def to_json(self) -> dict: + artifact_fields = self.artifact.model_dump() + return { + "artifact": artifact_fields, + "failed_fields": self.failed_artifact_fields, + } + + @classmethod + def from_json( + cls, + json_data: dict, + kernel: Kernel, + service_id: str, + input_artifact: BaseModel, + max_artifact_field_retries: int = 2, + ) -> "Artifact": + artifact = cls(kernel, service_id, input_artifact, max_artifact_field_retries) + + artifact.failed_artifact_fields = json_data["failed_fields"] + + # Iterate over artifact fields and set them to the values in the json data + # Skip any fields that are set as "Unanswered" + for field_name, field_value in json_data["artifact"].items(): + if field_value != "Unanswered": + setattr(artifact.artifact, field_name, field_value) + return artifact diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/base_model_llm.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/base_model_llm.py new file mode 100644 index 00000000..4c75f22c --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/base_model_llm.py @@ -0,0 +1,55 @@ +# Copyright (c) Microsoft. All rights reserved. + +# FIXME: Copied code from Semantic Kernel repo, using as-is despite type errors +# type: ignore + +import ast +from types import NoneType +from typing import Any, get_args + +from pydantic import BaseModel, ValidationInfo, field_validator + + +class BaseModelLLM(BaseModel): + """ + A Pydantic base class for use when an LLM is completing fields. Provides a + custom field validator and Pydantic Config. + """ + + @field_validator("*", mode="before") + def parse_literal_eval(cls, value: str, info: ValidationInfo) -> NoneType | str | Any: + """ + An LLM will always result in a string (e.g. '["x", "y"]'), so we need to + parse it to the correct type. + """ + + # Get the type hints for the field. + annotation = cls.model_fields[info.field_name].annotation + typehints = get_args(annotation) + if len(typehints) == 0: + typehints = [annotation] + + # Usually fields that are NoneType have another type hint as well, e.g. + # str | None. If the LLM returns "None" and the field allows NoneType, + # we should return None without this code, the next if-block would leave + # the string "None" as the value + if (NoneType in typehints) and (value == "None"): + return None + + # If the field allows strings, we don't parse it - otherwise a + # validation error might be raised e.g. phone_number = "1234567890" + # should not be converted to an int if the type hint is str + if str in typehints: + return value + try: + evaluated_value = ast.literal_eval(value) + return evaluated_value + except Exception: + return value + + class Config: + # Ensure that validation happens every time a field is updated, not just + # when the artifact is created. + validate_assignment = True + # Do not allow extra fields to be added to the artifact. + extra = "forbid" diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_final_update.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_final_update.py new file mode 100644 index 00000000..46150c99 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_final_update.py @@ -0,0 +1,82 @@ +import logging + +from form_filler_skill.guided_conversation.artifact import Artifact +from form_filler_skill.guided_conversation.conversation_helpers import Conversation +from form_filler_skill.guided_conversation.definition import GCDefinition +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider + +logger = logging.getLogger(__name__) + + +final_update_template = """You are a helpful, thoughtful, and meticulous assistant. +You just finished a conversation with a user.{% if context %} Here is some additional context about the conversation: +{{ context }}{% endif %} + +Your goal is to complete an artifact as thoroughly and accurately as possible based on the conversation. + +This is the schema of the artifact: +{{ artifact_schema }} + +You will be given the current state of the artifact as well as the conversation history. +Note that if the value for a field in the artifact is 'Unanswered', it means that the field was not completed. \ +Some fields may have already been completed during the conversation. + +Your need to determine whether there are any fields that need to be updated, and if so, update them. +- You should only update a field if both of the following conditions are met: (a) the current state does NOT adequately reflect the conversation \ +and (b) you are able to submit a valid value for a field. \ +You are allowed to update completed fields, but you should only do so if the current state is inadequate, \ +e.g. the user corrected a mistake in their date of birth, but the artifact does not show the corrected version. \ +Remember that it's always an option to reset a field to "Unanswered" - this is often the best choice if the artifact contains incorrect information that cannot be corrected. \ +Do not submit a value that is identical to the current state of the field (e.g. if the field is already "Unanswered" and the user didn't provide any new information about it, you should not submit "Unanswered"). \ +- Make sure the value adheres to the constraints of the field as specified in the artifact schema. \ +If it's not possible to update a field with a valid value (e.g., the user provided an invalid date of birth), you should not update the field. +- If the artifact schema is open-ended (e.g. it asks you to rate how pressing the user's issue is, without specifying rules for doing so), \ +use your best judgment to determine whether you have enough information to complete the field based on the conversation. +- Prioritize accuracy over completion. You should never make up information or make assumptions in order to complete a field. \ +For example, if the field asks for a 10-digit phone number, and the user provided a 9-digit phone number, you should not add a digit to the phone number in order to complete the field. +- If the user wasn't able to provide all of the information needed to complete a field, \ +use your best judgment to determine if a partial answer is appropriate (assuming it adheres to the formatting requirements of the field). \ +For example, if the field asks for a description of symptoms along with details about when the symptoms started, but the user wasn't sure when their symptoms started, \ +it's better to record the information they do have rather than to leave the field unanswered (and to indicate that the user was unsure about the start date). +- It's possible to update multiple fields at once (assuming you're adhering to the above rules in all cases). It's also possible that no fields need to be updated. + +Your task is to state your step-by-step reasoning about what to update, followed by a final recommendation. +Someone else will be responsible for executing the updates and they will only have access to your output \ +(not any of the conversation history, artifact schema, or other context) so make sure to specify exactly which \ +fields to update and the values to update them with, or to state that no fields need to be updated. + + +Conversation history: +{{ conversation_history }} + +Current state of the artifact: +{{ artifact_state }}""" + + +async def final_update( + open_ai_client: AsyncOpenAI | AsyncAzureOpenAI, + definition: GCDefinition, + chat_history: Conversation, + artifact: Artifact, +): + history = InMemoryMessageHistoryProvider() + + history.append_system_message( + final_update_template, + { + "conversation_history": chat_history.get_repr_for_prompt(), + "context": definition.conversation_context, + "artifact_schema": artifact.get_schema_for_prompt(), + "artifact_state": artifact.get_artifact_for_prompt(), + }, + ) + + config = ChatDriverConfig( + openai_client=open_ai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_fix_agenda_error.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_fix_agenda_error.py new file mode 100644 index 00000000..8498d854 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_fix_agenda_error.py @@ -0,0 +1,59 @@ +import logging + +from form_filler_skill.guided_conversation.conversation_helpers import ( + Conversation, + ConversationMessageType, +) +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider + +logger = logging.getLogger(__name__) + +AGENDA_ERROR_CORRECTION_SYSTEM_TEMPLATE = """You are a helpful, thoughtful, and meticulous assistant. +You are conducting a conversation with a user. You tried to update the agenda, but the update was invalid. +You will be provided the history of your conversation with the user, \ +your previous attempt(s) at updating the agenda, and the error message(s) that resulted from your attempt(s). +Your task is to correct the update so that it is valid. \ +Your changes should be as minimal as possible - you are focused on fixing the error(s) that caused the update to be invalid. +Note that if the resource allocation is invalid, you must follow these rules: +1. You should not change the description of the first item (since it has already been executed), but you can change its resource allocation +2. For all other items, you can combine or split them, or assign them fewer or more resources, \ +but the content they cover collectively should not change (i.e. don't eliminate or add new topics). +For example, the invalid attempt was "item 1 = ask for date of birth (1 turn), item 2 = ask for phone number (1 turn), \ +item 3 = ask for phone type (1 turn), item 4 = explore treatment history (6 turns)", \ +and the error says you need to correct the total resource allocation to 7 turns. \ +A bad solution is "item 1 = ask for date of birth (1 turn), \ +item 2 = explore treatment history (6 turns)" because it eliminates the phone number and phone type topics. \ +A good solution is "item 1 = ask for date of birth (2 turns), item 2 = ask for phone number, phone type, +and treatment history (2 turns), item 3 = explore treatment history (3 turns)." + +Conversation history: +{{ conversation_history }} + +Previous attempts to update the agenda: +{{ previous_attempts }}""" + + +async def fix_agenda_error( + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + previous_attempts: str, + conversation: Conversation, +): + history = InMemoryMessageHistoryProvider() + + history.append_system_message( + AGENDA_ERROR_CORRECTION_SYSTEM_TEMPLATE, + { + "conversation_history": conversation.get_repr_for_prompt(exclude_types=[ConversationMessageType.REASONING]), + "previous_attempts": previous_attempts, + }, + ) + + config = ChatDriverConfig( + openai_client=openai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_update_agenda.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_update_agenda.py new file mode 100644 index 00000000..805af525 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_update_agenda.py @@ -0,0 +1,239 @@ +import logging + +from form_filler_skill.agenda import Agenda, AgendaItem +from form_filler_skill.guided_conversation.definition import GCDefinition +from form_filler_skill.message import Conversation +from form_filler_skill.resources import ( + GCResource, + ResourceConstraintMode, + ResourceConstraintUnit, + format_resource, +) +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider +from pydantic import ValidationError + +from ...artifact import Artifact +from ...chat_drivers.fix_agenda_error import fix_agenda_error +from ...chat_drivers.update_agenda_template import update_agenda_template + +logger = logging.getLogger(__name__) + + +def _get_termination_instructions(resource: GCResource): + """ + Get the termination instructions for the conversation. This is contingent on the resources mode, + if any, that is available. + + Assumes we're always using turns as the resource unit. + + Args: + resource (GCResource): The resource object. + + Returns: + str: the termination instructions + """ + # Termination condition under no resource constraints + if resource.resource_constraint is None: + return ( + "- You should pick this action as soon as you have completed the artifact to the best of your ability, the" + " conversation has come to a natural conclusion, or the user is not cooperating so you cannot continue the" + " conversation." + ) + + # Termination condition under exact resource constraints + if resource.resource_constraint.mode == ResourceConstraintMode.EXACT: + return ( + "- You should only pick this action if the user is not cooperating so you cannot continue the conversation." + ) + + # Termination condition under maximum resource constraints + elif resource.resource_constraint.mode == ResourceConstraintMode.MAXIMUM: + return ( + "- You should pick this action as soon as you have completed the artifact to the best of your ability, the" + " conversation has come to a natural conclusion, or the user is not cooperating so you cannot continue the" + " conversation." + ) + + else: + logger.error("Invalid resource mode provided.") + return "" + + +async def update_agenda( + openai_client: AsyncOpenAI | AsyncAzureOpenAI, + definition: GCDefinition, + chat_history: Conversation, + agenda: Agenda, + artifact: Artifact, + resource: GCResource, +) -> bool: + # STEP 1: Generate an updated agenda. + + # If there is a resource constraint and there's more than one turn left, + # include additional constraint instructions. + remaining_resource = resource.remaining_units if resource.remaining_units else 0 + resource_instructions = resource.get_resource_instructions() + if (resource_instructions != "") and (remaining_resource > 1): + match resource.get_resource_mode(): + case ResourceConstraintMode.MAXIMUM: + total_resource_str = f"does not exceed the remaining turns ({remaining_resource})." + ample_time_str = "" + case ResourceConstraintMode.EXACT: + total_resource_str = ( + f"is equal to the remaining turns ({remaining_resource}). Do not leave any turns unallocated." + ) + ample_time_str = ( + "If you have many turns remaining, instead of including wrap-up items or repeating " + "topics, you should include items that increase the breadth and/or depth of the conversation " + 'in a way that\'s directly relevant to the artifact (e.g. "collect additional details about X", ' + '"ask for clarification about Y", "explore related topic Z", etc.).' + ) + case _: + logger.error("Invalid resource mode.") + else: + total_resource_str = "" + ample_time_str = "" + + history = InMemoryMessageHistoryProvider() + history.append_system_message( + update_agenda_template, + { + "context": definition.conversation_context, + "artifact_schema": definition.artifact_schema, + "rules": definition.rules, + "current_state_description": definition.conversation_flow, + "show_agenda": True, + "remaining_resource": remaining_resource, + "total_resource_str": total_resource_str, + "ample_time_str": ample_time_str, + "termination_instructions": _get_termination_instructions(resource), + "resource_instructions": resource_instructions, + "chat_history": chat_history, + "agenda_state": get_agenda_for_prompt(agenda), + "artifact_state": artifact.get_artifact_for_prompt(), + }, + ) + + config = ChatDriverConfig( + openai_client=openai_client, + model="gpt-4o", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + response = await chat_driver.respond() + items = response.message + + # STEP 2: Validate/fix the updated agenda. + + previous_attempts = [] + while True: + try: + # Pydantic type validation. + agenda.items = items # type: ignore + + # Check resource constraints. + if agenda.resource_constraint_mode is not None: + check_item_constraints( + agenda.resource_constraint_mode, + agenda.items, + resource.estimate_remaining_turns(), + ) + + logger.info(f"Agenda updated successfully: {get_agenda_for_prompt(agenda)}") + return True + + except (ValidationError, ValueError) as e: + # If we have reached the maximum number of retries return a failure. + if len(previous_attempts) >= agenda.max_agenda_retries: + logger.warning(f"Failed to update agenda after {agenda.max_agenda_retries} attempts.") + return False + + # Otherwise, get an error string. + if isinstance(e, ValidationError): + error_str = "; ".join([e.get("msg") for e in e.errors()]) + error_str = error_str.replace("; Input should be 'Unanswered'", " or input should be 'Unanswered'") + else: + error_str = str(e) + + # Add it to our list of previous attempts. + previous_attempts.append((str(items), error_str)) + + # And try again. + logger.info(f"Attempting to fix the agenda error. Attempt {len(previous_attempts)}.") + llm_formatted_attempts = "\n".join([ + f"Attempt: {attempt}\nError: {error}" for attempt, error in previous_attempts + ]) + response = await fix_agenda_error(openai_client, llm_formatted_attempts, chat_history) + + if response is None: + raise ValueError("Invalid response from the LLM.") + + # if response["validation_result"] != "success": # ToolValidationResult.SUCCESS: + # logger.warning( + # f"Failed to fix the agenda error due to a failure in the LLM tool call: {response['validation_result']}" + # ) + # return False + # else: + # # Use the result of the first tool call to try the update again + # items = response + items = response + + +def check_item_constraints( + resource_constraint_mode: ResourceConstraintMode, + items: list[AgendaItem], + remaining_turns: int, +) -> None: + """ + Validates if any constraints were violated while performing the agenda + update. + """ + # The total, proposed allocation of resources. + total_resources = sum([item.resource for item in items]) + + violations = [] + # In maximum mode, the total resources should not exceed the remaining + # turns. + if (resource_constraint_mode == ResourceConstraintMode.MAXIMUM) and (total_resources > remaining_turns): + violations.append( + "The total turns allocated in the agenda " + f"must not exceed the remaining amount ({remaining_turns}); " + f"but the current total is {total_resources}." + ) + + # In exact mode if the total resources were not exactly equal to the + # remaining turns. + if (resource_constraint_mode == ResourceConstraintMode.EXACT) and (total_resources != remaining_turns): + violations.append( + "The total turns allocated in the agenda " + f"must equal the remaining amount ({remaining_turns}); " + f"but the current total is {total_resources}." + ) + + # Check if any item has a resource value of 0. + if any(item.resource <= 0 for item in items): + violations.append("All items must have a resource value greater than 0.") + + # Raise an error if any violations were found. + if len(violations) > 0: + logger.debug(f"Agenda update failed due to the following violations: {violations}.") + raise ValueError(" ".join(violations)) + + +def get_agenda_for_prompt(agenda: Agenda) -> str: + """ + Gets a string representation of the agenda for use in an LLM prompt. + """ + agenda_json = agenda.model_dump() + agenda_items = agenda_json.get("items", []) + if len(agenda_items) == 0: + return "None" + agenda_str = "\n".join([ + f"{i + 1}. [{format_resource(item['resource'], ResourceConstraintUnit.TURNS)}] {item['title']}" + for i, item in enumerate(agenda_items) + ]) + total_resource = format_resource(sum([item["resource"] for item in agenda_items]), ResourceConstraintUnit.TURNS) + agenda_str += f"\nTotal = {total_resource}" + return agenda_str diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_update_artifact.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_update_artifact.py new file mode 100644 index 00000000..c43f9e85 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/chat_drivers/gc_update_artifact.py @@ -0,0 +1,57 @@ +import logging + +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai_client.chat_driver import ChatDriver, ChatDriverConfig, InMemoryMessageHistoryProvider + +logger = logging.getLogger(__name__) + +execution_template = """You are a helpful, thoughtful, and meticulous assistant. +You are conducting a conversation with a user. Your goal is to complete an artifact as thoroughly as possible by the +end of the conversation. +You will be given some reasoning about the best possible action(s) to take next given the state of the conversation +as well as the artifact schema. +The reasoning is supposed to state the recommended action(s) to take next, along with all required parameters for each action. +Your task is to execute ALL actions recommended in the reasoning in the order they are listed. +If the reasoning's specification of an action is incomplete (e.g. it doesn't include all required parameters for the +action, \ +or some parameters are specified implicitly, such as "send a message that contains a greeting" instead of explicitly +providing \ +the value of the "message" parameter), do not execute the action. You should never fill in missing or imprecise +parameters yourself. +If the reasoning is not clear about which actions to take, or all actions are specified in an incomplete way, \ +return 'None' without selecting any action. + +Artifact schema: +{{ artifact_schema }} + +If the type in the schema is str, the "field_value" parameter in the action should be also be a string. +These are example parameters for the update_artifact action: {"field_name": "company_name", "field_value": "Contoso"} +DO NOT write JSON in the "field_value" parameter in this case. {"field_name": "company_name", "field_value": "{"value": "Contoso"}"} is INCORRECT. + +Reasoning: +{{ reasoning }}""" + + +async def update_artifact( + open_ai_client: AsyncOpenAI | AsyncAzureOpenAI, + reasoning: str, + artifact_schema: str, +): + history = InMemoryMessageHistoryProvider() + + history.append_system_message( + execution_template, + { + "artifact_schema": artifact_schema, + "reasoning": reasoning, + }, + ) + + config = ChatDriverConfig( + openai_client=open_ai_client, + model="gpt-3.5-turbo", + message_provider=history, + ) + + chat_driver = ChatDriver(config) + return await chat_driver.respond() diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/conversation_helpers.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/conversation_helpers.py new file mode 100644 index 00000000..1b9502d3 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/conversation_helpers.py @@ -0,0 +1,166 @@ +# Copyright (c) Microsoft. All rights reserved. + +# FIXME: Copied code from Semantic Kernel repo, using as-is despite type errors +# type: ignore + +import logging +import time +from dataclasses import dataclass, field +from enum import StrEnum +from typing import Union + +from semantic_kernel.contents import ChatMessageContent + + +class ConversationMessageType(StrEnum): + DEFAULT = "default" + ARTIFACT_UPDATE = "artifact-update" + REASONING = "reasoning" + + +@dataclass +class Conversation: + """An abstraction to represent a list of messages and common operations such as adding messages + and getting a string representation. + + Args: + conversation_messages (list[ChatMessageContent]): A list of ChatMessageContent objects. + """ + + logger = logging.getLogger(__name__) + conversation_messages: list[ChatMessageContent] = field(default_factory=list) + + def add_messages(self, messages: Union[ChatMessageContent, list[ChatMessageContent], "Conversation", None]) -> None: + """Add a message, list of messages to the conversation or merge another conversation into the end of this one. + + Args: + messages (Union[ChatMessageContent, list[ChatMessageContent], "Conversation"]): The message(s) to add. + All messages will be added to the end of the conversation. + + Returns: + None + """ + if isinstance(messages, list): + self.conversation_messages.extend(messages) + elif isinstance(messages, Conversation): + self.conversation_messages.extend(messages.conversation_messages) + elif isinstance(messages, ChatMessageContent): + # if ChatMessageContent.metadata doesn't have type, then add default + if "type" not in messages.metadata: + messages.metadata["type"] = ConversationMessageType.DEFAULT + self.conversation_messages.append(messages) + else: + self.logger.warning(f"Invalid message type: {type(messages)}") + return None + + def get_repr_for_prompt( + self, + exclude_types: list[ConversationMessageType] | None = None, + ) -> str: + """Create a string representation of the conversation history for use in LLM prompts. + + Args: + exclude_types (list[ConversationMessageType] | None): A list of message types to exclude from the conversation + history. If None, all message types will be included. + + Returns: + str: A string representation of the conversation history. + """ + if len(self.conversation_messages) == 0: + return "None" + + # Do not include the excluded messages types in the conversation history repr. + if exclude_types is not None: + conversation_messages = [ + message + for message in self.conversation_messages + if "type" in message.metadata and message.metadata["type"] not in exclude_types + ] + else: + conversation_messages = self.conversation_messages + + to_join = [] + current_turn = None + for message in conversation_messages: + participant_name = message.name + # Modify the default user to be capitalized for consistency with how assistant is written. + if participant_name == "user": + participant_name = "User" + + # If the turn number is None, don't include it in the string + if "turn_number" in message.metadata and current_turn != message.metadata["turn_number"]: + current_turn = message.metadata["turn_number"] + to_join.append(f"[Turn {current_turn}]") + + # Add the message content + if (message.role == "assistant") and ( + "type" in message.metadata and message.metadata["type"] == ConversationMessageType.ARTIFACT_UPDATE + ): + to_join.append(message.content) + elif message.role == "assistant": + to_join.append(f"Assistant: {message.content}") + else: + user_string = message.content.strip() + if user_string == "": + to_join.append(f"{participant_name}: ") + else: + to_join.append(f"{participant_name}: {user_string}") + + conversation_string = "\n".join(to_join) + return conversation_string + + def message_to_json(self, message: ChatMessageContent) -> dict: + """ + Convert a ChatMessageContent object to a JSON serializable dictionary. + + Args: + message (ChatMessageContent): The ChatMessageContent object to convert to JSON. + + Returns: + dict: A JSON serializable dictionary representation of the ChatMessageContent object. + """ + return { + "role": message.role, + "content": message.content, + "name": message.name, + "metadata": { + "turn_number": message.metadata["turn_number"] if "turn_number" in message.metadata else None, + "type": message.metadata["type"] if "type" in message.metadata else ConversationMessageType.DEFAULT, + "timestamp": message.metadata["timestamp"] if "timestamp" in message.metadata else None, + }, + } + + def to_json(self) -> dict: + json_data = {} + json_data["conversation"] = {} + json_data["conversation"]["conversation_messages"] = [ + self.message_to_json(message) for message in self.conversation_messages + ] + return json_data + + @classmethod + def from_json( + cls, + json_data: dict, + ) -> "Conversation": + conversation = cls() + for message in json_data["conversation"]["conversation_messages"]: + conversation.add_messages( + ChatMessageContent( + role=message["role"], + content=message["content"], + name=message["name"], + metadata={ + "turn_number": message["metadata"]["turn_number"], + "type": ConversationMessageType(message["metadata"]["type"]), + "timestamp": message["metadata"]["timestamp"], + }, + ) + ) + + return conversation + + +def create_formatted_timestamp() -> str: + """Create a formatted timestamp.""" + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/definition.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/definition.py new file mode 100644 index 00000000..a8befa22 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/definition.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from typing import Optional + +from form_filler_skill.guided_conversation.resources import ResourceConstraint + + +@dataclass +class GCDefinition: + artifact_schema: str + rules: str + conversation_flow: Optional[str] + conversation_context: str + resource_constraint: Optional[ResourceConstraint] + service_id: str = "gc_main" diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/openai_tool_calling.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/openai_tool_calling.py new file mode 100644 index 00000000..1e567896 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/openai_tool_calling.py @@ -0,0 +1,167 @@ +# Copyright (c) Microsoft. All rights reserved. + +# FIXME: Copied code from Semantic Kernel repo, using as-is despite type errors +# type: ignore + +import json +import logging +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from semantic_kernel.contents.function_call_content import FunctionCallContent +from semantic_kernel.functions import FunctionResult + +logger = logging.getLogger(__name__) + + +@dataclass +class ToolArg: + argument_name: str + required: bool + + +@dataclass +class Tool: + name: str + args: list[ToolArg] + + +class ToolValidationResult(Enum): + NO_TOOL_CALLED = "No tool was called" + INVALID_TOOL_CALLED = "A tool was called with an unexpected name" + MISSING_REQUIRED_ARGUMENT = "The tool called is missing a required argument" + INVALID_ARGUMENT_TYPE = "The value of an argument is of an unexpected type" + INVALID_ARGUMENT = "The tool called has an unexpected argument" + SUCCESS = "success" + + +def parse_function_result(response: FunctionResult) -> dict[str, Any]: + """Parse the response from SK's FunctionResult object into only the relevant data for easier downstream processing. + This should only be used when you expect the response to contain tool calls. + + Args: + response (FunctionResult): The response from the kernel. + + Returns: + dict[str, Any]: The parsed response data with the following format if n was set greater than 1: + { + "choices": [ + { + "tool_names": list[str], + "tool_args_list": list[dict[str, Any]], + "message": str, + "finish_reason": str, + "validation_result": ToolValidationResult + }, ... + ] + } + Otherwise, the response will directly contain the data from the first choice (tool_names, etc keys) + """ + response_data: dict[str, Any] = {"choices": []} + for response_choice in response.value: + response_data_curr = {} + finish_reason = response_choice.finish_reason + + if finish_reason == "tool_calls": + tool_names = [] + tool_args_list = [] + # Only look at the items that are of instance `FunctionCallContent` + tool_calls = [item for item in response_choice.items if isinstance(item, FunctionCallContent)] + for tool_call in tool_calls: + if "-" not in tool_call.name: + logger.info(f"Tool call name {tool_call.name} does not match naming convention - modifying name.") + tool_names.append(tool_call.name + "-" + tool_call.name) + else: + tool_names.append(tool_call.name) + try: + tool_args = json.loads(tool_call.arguments) + except json.JSONDecodeError: + logger.warning(f"Failed to parse tool arguments for tool call {tool_call.name}. Using empty dict.") + tool_args = {} + tool_args_list.append(tool_args) + response_data_curr["tool_names"] = tool_names + response_data_curr["tool_args_list"] = tool_args_list + + response_data_curr["message"] = response_choice.content + response_data_curr["finish_reason"] = finish_reason + response_data["choices"].append(response_data_curr) + + if len(response_data["choices"]) == 1: + response_data.update(response_data["choices"][0]) + del response_data["choices"] + + return response_data + + +def construct_tool_objects(kernel_function_tools: dict) -> list[Tool]: + """Construct a list of Tool objects from the kernel function tools definition. + + Args: + kernel_function_tools (dict): The definition of tools done by the kernel function. + + Returns: + list[Tool]: The list of Tool objects constructed from the kernel function tools definition. + """ + + tool_objects: list[Tool] = [] + for tool_definition in kernel_function_tools: + tool_name = tool_definition["function"]["name"] + tool_args = tool_definition["function"]["parameters"]["properties"] + + tool_arg_objects: list[ToolArg] = [] + for argument_name, _ in tool_args.items(): + tool_arg = ToolArg(argument_name=argument_name, required=False) + tool_arg_objects.append(tool_arg) + + required_args = tool_definition["function"]["parameters"]["required"] + for tool_arg_object in tool_arg_objects: + if tool_arg_object.argument_name in required_args: + tool_arg_object.required = True + + tool_objects.append(Tool(name=tool_name, args=tool_arg_objects)) + return tool_objects + + +def validate_tool_calling(response: dict[str, Any], request_tool_param: dict) -> ToolValidationResult: + """Validate that the response from the LLM called tools corrected. + 1. Check if any tool was called. + 2. Check if the tools called were valid (names match) + 3. Check if all the required arguments were passed. + + Args: + response (dict[str, Any]): The response from the LLM containing the tools called (output of parse_function_result) + tools (list[Tool]): The list of tools that can be called by the model. + + Returns: + ToolValidationResult: The result of the validation. ToolValidationResult.SUCCESS if the validation passed. + """ + + tool_objects = construct_tool_objects(request_tool_param) + tool_names = response.get("tool_names", []) + tool_args_list = response.get("tool_args_list", []) + + # Check if any tool was called. + if not tool_names: + logger.info("No tool was called.") + return ToolValidationResult.NO_TOOL_CALLED + + for tool_name, tool_args in zip(tool_names, tool_args_list, strict=True): + # Check the tool names is valid. + tool: Tool | None = next((t for t in tool_objects if t.name == tool_name), None) + if not tool: + logger.warning(f"Invalid tool called: {tool_name}") + return ToolValidationResult.INVALID_TOOL_CALLED + + for arg in tool.args: + # Check if the required arguments were passed. + if arg.required and arg.argument_name not in tool_args: + logger.warning(f"Missing required argument '{arg.argument_name}' for tool '{tool_name}'.") + return ToolValidationResult.MISSING_REQUIRED_ARGUMENT + + for tool_arg_name in tool_args.keys(): + if tool_arg_name not in [arg.argument_name for arg in tool.args]: + logger.warning(f"Unexpected argument '{tool_arg_name}' for tool '{tool_name}'.") + return ToolValidationResult.INVALID_ARGUMENT + + return ToolValidationResult.SUCCESS diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/plugin_helpers.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/plugin_helpers.py new file mode 100644 index 00000000..ba32e4df --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/plugin_helpers.py @@ -0,0 +1,69 @@ +# # Copyright (c) Microsoft. All rights reserved. + +# FIXME: Copied code from Semantic Kernel repo, using as-is despite type errors +# type: ignore + +from dataclasses import dataclass + +from pydantic import ValidationError +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai.function_calling_utils import kernel_function_metadata_to_function_call_format +from semantic_kernel.contents import ChatMessageContent + + +@dataclass +class PluginOutput: + """A wrapper for all Guided Conversation Plugins. This class is used to return the output of a generic plugin. + + Args: + update_successful (bool): Whether the update was successful. + messages (list[ChatMessageContent]): A list of messages to be used at the user's digression, it + contains information about the process of calling the plugin. + """ + + update_successful: bool + messages: list[ChatMessageContent] + + +def format_kernel_functions_as_tools(kernel: Kernel, functions: list[str]): + """Format kernel functions as JSON schemas for custom validation.""" + formatted_tools = [] + for _, kernel_plugin_def in kernel.plugins.items(): + for function_name, function_def in kernel_plugin_def.functions.items(): + if function_name in functions: + func_metadata = function_def.metadata + formatted_tools.append(kernel_function_metadata_to_function_call_format(func_metadata)) + return formatted_tools + + +def update_attempts( + error: Exception, attempt_id: str, previous_attempts: list[tuple[str, str]] +) -> tuple[list[tuple[str, str]], str]: + """ + Updates the plugin class attribute list of previous attempts with the current attempt and error message + (including duplicates). + + Args: + error (Exception): The error object. + attempt_id (str): The ID of the current attempt. + previous_attempts (list): The list of previous attempts. + + Returns: + str: A formatted (optimized for LLM performance) string of previous attempts, with duplicates removed. + """ + if isinstance(error, ValidationError): + error_str = "; ".join([e.get("msg") for e in error.errors()]) + # replace "; Input should be 'Unanswered'" with " or input should be 'Unanswered'" for clarity + error_str = error_str.replace("; Input should be 'Unanswered'", " or input should be 'Unanswered'") + else: + error_str = str(error) + new_failed_attempt = (attempt_id, error_str) + previous_attempts.append(new_failed_attempt) + + # Format previous attempts to be more friendly for the LLM + attempts_list = [] + for attempt, error in previous_attempts: + attempts_list.append(f"Attempt: {attempt}\nError: {error}") + llm_formatted_attempts = "\n".join(attempts_list) + + return previous_attempts, llm_formatted_attempts diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/resources.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/resources.py new file mode 100644 index 00000000..f39d0253 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation/resources.py @@ -0,0 +1,285 @@ +# Copyright (c) Microsoft. All rights reserved. + + +import logging +import math +import time +from enum import StrEnum + +from pydantic import BaseModel + +logger = logging.getLogger(__name__) + + +class ResourceConstraintUnit(StrEnum): + """ + Choose the unit of the resource constraint. Seconds and Minutes are + real-time and will be impacted by the latency of the model. + """ + + SECONDS = "seconds" + MINUTES = "minutes" + TURNS = "turns" + + +class ResourceConstraintMode(StrEnum): + """ + Choose how the agent should use the resource. + + Maximum: is an upper bound, i.e. the agent can end the conversation before + the resource is exhausted. + + Exact: The agent should aim to use exactly the given amount of the resource. + """ + + MAXIMUM = "maximum" + EXACT = "exact" + + +class ResourceConstraint(BaseModel): + """ + A structured representation of the resource constraint for the + GuidedConversation agent. + """ + + quantity: float | int + unit: ResourceConstraintUnit + mode: ResourceConstraintMode + + class Config: + arbitrary_types_allowed = True + + +def format_resource(quantity: float, unit: ResourceConstraintUnit) -> str: + """ + Get formatted string for a given quantity and unit (e.g. 1 second, 20 + seconds) + """ + if unit != ResourceConstraintUnit.TURNS: + quantity = round(quantity, 1) + if quantity == 1: + return f"{quantity} {unit.value.rstrip('s')}" + else: + return f"{quantity} {unit.value}" + + +class GCResource: + """ + Resource constraints for the GuidedConversation agent. This class is used to + keep track of the resource constraints. If resource_constraint is None, then + the agent can continue indefinitely. This also means that no agenda will be + created for the conversation. + + Args: + resource_constraint (ResourceConstraint | None): The resource constraint + for the conversation. + + initial_seconds_per_turn (int): The initial number of seconds per turn. + Defaults to 120 seconds. + """ + + def __init__( + self, + resource_constraint: ResourceConstraint | None, + initial_seconds_per_turn: int = 120, + ): + self.resource_constraint: ResourceConstraint | None = resource_constraint + self.initial_seconds_per_turn: int = initial_seconds_per_turn + + self.turn_number: int = 0 + + if resource_constraint is not None: + self.elapsed_units = 0 + self.remaining_units = resource_constraint.quantity + else: + self.elapsed_units = 0 + self.remaining_units = 0 + + def start_resource(self) -> None: + """To be called at the start of a conversation turn""" + if self.resource_constraint is not None and ( + self.resource_constraint.unit == ResourceConstraintUnit.SECONDS + or self.resource_constraint.unit == ResourceConstraintUnit.MINUTES + ): + self.start_time = time.time() + + def increment_resource(self) -> None: + """Increment the resource counter by one turn.""" + if self.resource_constraint is not None: + match self.resource_constraint.unit: + case ResourceConstraintUnit.SECONDS: + self.elapsed_units += time.time() - self.start_time + self.remaining_units = self.resource_constraint.quantity - self.elapsed_units + case ResourceConstraintUnit.MINUTES: + self.elapsed_units += (time.time() - self.start_time) / 60 + self.remaining_units = self.resource_constraint.quantity - self.elapsed_units + case ResourceConstraintUnit.TURNS: + self.elapsed_units += 1 + self.remaining_units -= 1 + case _: + raise ValueError("Invalid resource unit provided.") + self.turn_number += 1 + + def get_resource_mode(self) -> ResourceConstraintMode | None: + """ + Get the mode of the resource constraint. + """ + if self.resource_constraint is None: + return None + return self.resource_constraint.mode + + def get_elapsed_turns(self, formatted_repr: bool = False) -> str | int: + """ + Get the number of elapsed turns. + + Args: + formatted_repr (bool): If true, return a formatted string + representation of the elapsed turns. If false, return an integer. + Defaults to False. + + Returns: + str | int: The description/number of elapsed turns. + """ + if formatted_repr: + return format_resource(self.turn_number, ResourceConstraintUnit.TURNS) + else: + return self.turn_number + + def get_remaining_turns(self, formatted_repr: bool = False) -> str | int: + """ + Get the number of remaining turns. + + Args: + formatted_repr (bool): If true, return a formatted string + representation of the remaining turns. + + Returns: + str | int: The description/number of remaining turns. + """ + if formatted_repr: + return format_resource(self.estimate_remaining_turns(), ResourceConstraintUnit.TURNS) + else: + return self.estimate_remaining_turns() + + def estimate_remaining_turns(self) -> int: + """ + Estimate the remaining turns based on the resource constraint, thereby + translating certain resource units (e.g. seconds, minutes) into turns. + """ + if self.resource_constraint is None: + logger.error( + "Resource constraint is not set, so turns cannot be estimated using function estimate_remaining_turns" + ) + raise ValueError( + "Resource constraint is not set. Do not try to call this method without a resource constraint." + ) + + match self.resource_constraint.unit: + case ResourceConstraintUnit.MINUTES: + if self.turn_number == 0: + time_per_turn = self.initial_seconds_per_turn + else: + time_per_turn = (self.elapsed_units * 60) / self.turn_number + time_per_turn /= 60 + remaining_turns = self.remaining_units / time_per_turn + if remaining_turns < 1: + return math.ceil(remaining_turns) + else: + return math.floor(remaining_turns) + + case ResourceConstraintUnit.SECONDS: + if self.turn_number == 0: + time_per_turn = self.initial_seconds_per_turn + else: + time_per_turn = self.elapsed_units / self.turn_number + remaining_turns = self.remaining_units / time_per_turn + if remaining_turns < 1: + return math.ceil(remaining_turns) + else: + return math.floor(remaining_turns) + + case ResourceConstraintUnit.TURNS: + return int(self.resource_constraint.quantity - self.turn_number) + + case _: + raise ValueError("Invalid resource unit provided.") + + def get_resource_instructions(self) -> str: + """Get the resource instructions for the conversation. + + Assumes we're always using turns as the resource unit. + + Returns: + str: the resource instructions + """ + if self.resource_constraint is None: + return "" + + formatted_elapsed_resource = format_resource( + self.elapsed_units, ResourceConstraintUnit.TURNS + ) + formatted_remaining_resource = format_resource( + self.remaining_units, ResourceConstraintUnit.TURNS + ) + + # if the resource quantity is anything other than 1, the resource unit should be plural (e.g. "minutes" instead of "minute") + is_plural_elapsed = self.elapsed_units != 1 + is_plural_remaining = self.remaining_units != 1 + + if self.elapsed_units > 0: + resource_instructions = f"So far, {formatted_elapsed_resource} {'have' if is_plural_elapsed else 'has'} elapsed since the conversation began. " + else: + resource_instructions = "" + + if self.resource_constraint.mode == ResourceConstraintMode.EXACT: + exact_mode_instructions = ( + f"There {'are' if is_plural_remaining else 'is'} {formatted_remaining_resource} " + "remaining (including this one) - the conversation will automatically terminate " + "when 0 turns are left. You should continue the conversation until it is " + "automatically terminated. This means you should NOT preemptively end the " + 'conversation, either explicitly (by selecting the "End conversation" action) ' + "or implicitly (e.g. by telling the user that you have all required information " + "and they should wait for the next step). Your goal is not to maximize efficiency " + "(i.e. complete the artifact as quickly as possible then end the conversation), " + "but rather to make the best use of ALL remaining turns available to you" + ) + + if is_plural_remaining: + resource_instructions += f"""{exact_mode_instructions}. This will require you to plan your actions carefully using the agenda: you want to avoid the situation where you have to pack too many topics into the final turns because you didn't account for them earlier, \ +or where you've rushed through the conversation and all fields are completed but there are still many turns left.""" + + # special instruction for the final turn (i.e. 1 remaining) in exact mode + else: + resource_instructions += f"""{exact_mode_instructions}, including this one. Therefore, you should use this turn to ask for any remaining information needed to complete the artifact, \ + or, if the artifact is already completed, continue to broaden/deepen the discussion in a way that's directly relevant to the artifact. Do NOT indicate to the user that the conversation is ending.""" + + elif self.resource_constraint.mode == ResourceConstraintMode.MAXIMUM: + resource_instructions += f"""You have a maximum of {formatted_remaining_resource} (including this one) left to complete the conversation. \ +You can decide to terminate the conversation at any point (including now), otherwise the conversation will automatically terminate when 0 turns are left. \ +You will need to plan your actions carefully using the agenda: you want to avoid the situation where you have to pack too many topics into the final turns because you didn't account for them earlier.""" + + else: + logger.error("Invalid resource mode provided.") + + return resource_instructions + + def to_json(self) -> dict: + return { + "turn_number": self.turn_number, + "remaining_units": self.remaining_units, + "elapsed_units": self.elapsed_units, + } + + @classmethod + def from_json( + cls, + json_data: dict, + ) -> "GCResource": + gc_resource = cls( + resource_constraint=None, + initial_seconds_per_turn=120, + ) + gc_resource.turn_number = json_data["turn_number"] + gc_resource.remaining_units = json_data["remaining_units"] + gc_resource.elapsed_units = json_data["elapsed_units"] + return gc_resource diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation_skill.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation_skill.py new file mode 100644 index 00000000..f17aa673 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/guided_conversation_skill.py @@ -0,0 +1,154 @@ +# flake8: noqa +# ruff: noqa + +from typing import Any, Optional + +from openai_client.chat_driver import ChatDriverConfig +from events import BaseEvent, MessageEvent +from skill_library import EmitterType, FunctionRoutine, RoutineTypes, Skill +from skill_library.run_context import RunContext + +from form_filler_skill.agenda import Agenda +from form_filler_skill.artifact import Artifact +from form_filler_skill.definition import GCDefinition +from form_filler_skill.message import Conversation +from form_filler_skill.resources import GCResource + +from .chat_drivers.final_update import final_update +from .chat_drivers.unneeded.execute_reasoning import execute_reasoning +from .chat_drivers.update_agenda import update_agenda + +NAME = "guided-conversation" +CLASS_NAME = "GuidedConversationSkill" +DESCRIPTION = "Walks the user through a conversation about gathering info for the creation of an artifact." +DEFAULT_MAX_RETRIES = 3 +INSTRUCTIONS = "You are an assistant." + + +class GuidedConversationSkill(Skill): + def __init__( + self, + chat_driver_config: ChatDriverConfig, + emit: EmitterType, + agenda: Agenda, + artifact: Artifact, + resource: GCResource, + ) -> None: + # Put all functions in a group. We are going to use all these as (1) + # skill actions, but also as (2) chat functions and (3) chat commands. + # You can also put them in separate lists if you want to differentiate + # between these. + functions = [ + self.update_agenda, + self.execute_reasoning, + self.final_update, + ] + + # Add some skill routines. + routines: list[RoutineTypes] = [ + self.conversation_routine(), + ] + + # Configure the skill's chat driver. + # TODO: change where this is from. + self.openai_client = chat_driver_config.openai_client + chat_driver_config.instructions = INSTRUCTIONS + chat_driver_config.commands = functions + chat_driver_config.functions = functions + + # TODO: Persist these. They should be saved in the skills state by + # session_id. + self.agenda = agenda + self.artifact = artifact + self.resource = resource + self.chat_history = Conversation() + + self.emit = emit + + # Initialize the skill! + super().__init__( + name=NAME, + description=DESCRIPTION, + chat_driver_config=chat_driver_config, + skill_actions=functions, + routines=routines, + ) + + ################################## + # Routines + ################################## + + def conversation_routine(self) -> FunctionRoutine: + return FunctionRoutine( + name="conversation", + description="Run a guided conversation.", + init_function=self.conversation_init_function, + step_function=self.conversation_step_function, + skill=self, + ) + + async def conversation_init_function(self, context: RunContext, vars: dict[str, Any] | None = None): + if vars is None: + return + state = {"definition": vars["definition"]} + await context.routine_stack.set_current_state(state) + await self.conversation_step_function(context) + + async def conversation_step_function( + self, + context: RunContext, + message: Optional[str] = None, + ): + # TODO: Where is this conversation maintained? + # FIXME: None of this works. WIP. + frame = await context.routine_stack.peek() + state = frame.state if frame else {} + definition = GCDefinition(**state["definition"]) + while True: + match state["mode"]: + case None: + state["mode"] = "init" + case "init": + state["chat_history"] = [] + agenda, done = await self.update_agenda("") + if done: + state["mode"] = "finalize" + state["mode"] = "conversation" + self.emit(MessageEvent(message="Agenda updated")) + return + case "conversation": + state["chat_history"] += message + # state["artifact"] = self.update_artifact(context) + agenda, done = await self.update_agenda("") + if agenda: + state["agenda"] = agenda + if done: + state["mode"] = "finalize" + # await self.message_user(context, agenda) # generates the next message + return + case "finalize": + # self.final_update() # Generates the final message. + state["state"] = "done" + # runner.send(message) + return + case "done": + return state["artifact"] + + ################################## + # Actions + ################################## + + async def update_agenda(self, items: str) -> tuple[Agenda, bool]: + return await update_agenda( + self.openai_client, + items, + self.chat_history, + self.agenda, + self.resource, + ) + + async def execute_reasoning(self, context: RunContext, reasoning: str) -> BaseEvent: + return await execute_reasoning(self.openai_client, reasoning, self.artifact.get_schema_for_prompt()) + + async def final_update(self, context: RunContext, definition: GCDefinition): + await final_update(self.openai_client, definition, self.chat_history, self.artifact) diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/message.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/message.py new file mode 100644 index 00000000..4b128656 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/message.py @@ -0,0 +1,61 @@ +from enum import StrEnum + +from attr import dataclass +from openai.types.chat import ChatCompletionMessageParam + + +class ConversationMessageType(StrEnum): + DEFAULT = "default" + ARTIFACT_UPDATE = "artifact-update" + REASONING = "reasoning" + + +class Message: + def __init__( + self, + chat_completion_message_param: ChatCompletionMessageParam, + type: ConversationMessageType | None = None, + turn: int | None = None, + ) -> None: + self.param = chat_completion_message_param + self.turn = turn + self.type = type or ConversationMessageType.DEFAULT + + +@dataclass +class Conversation: + messages: list[Message] = [] + + def exclude(self, types: list[ConversationMessageType]) -> list[Message]: + return [message for message in self.messages if message.type not in types] + + def __str__(self) -> str: + message_strs = [] + current_turn = None + for message in self.messages: + # Modify the default user to be capitalized for consistency with how + # assistant is written. + name = message.param["role"] + if name == "user": + name = "User" + + # Append the turn number if it has changed. + if message.turn is not None and current_turn != message.turn: + current_turn = message.turn + message_strs.append(f"[Turn {current_turn}]") + + # Append the message content. + content = message.param.get("content", "") + if message.param["role"] == "assistant": + if message.type == ConversationMessageType.ARTIFACT_UPDATE: + message_strs.append(content) + else: + message_strs.append(f"Assistant: {content}") + else: + user_string = str(content).strip() + if user_string == "": + message_strs.append(f"{name}: ") + else: + message_strs.append(f"{name}: {user_string}") + + return "\n".join(message_strs) diff --git a/libraries/python/skills/skills/form-filler-skill/form_filler_skill/resources.py b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/resources.py new file mode 100644 index 00000000..7d0b5f58 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/form_filler_skill/resources.py @@ -0,0 +1,275 @@ +# Copyright (c) Microsoft. All rights reserved. + + +import logging +import math +import time +from enum import StrEnum + +from pydantic import BaseModel + +logger = logging.getLogger(__name__) + + +class ResourceConstraintUnit(StrEnum): + """ + Choose the unit of the resource constraint. Seconds and Minutes are + real-time and will be impacted by the latency of the model. + """ + + SECONDS = "seconds" + MINUTES = "minutes" + TURNS = "turns" + + +class ResourceConstraintMode(StrEnum): + """ + Choose how the agent should use the resource. + + Maximum: is an upper bound, i.e. the agent can end the conversation before + the resource is exhausted. + + Exact: The agent should aim to use exactly the given amount of the resource. + """ + + MAXIMUM = "maximum" + EXACT = "exact" + + +class ResourceConstraint(BaseModel): + """ + A structured representation of the resource constraint for the + GuidedConversation agent. + """ + + quantity: float | int + unit: ResourceConstraintUnit + mode: ResourceConstraintMode + + class Config: + arbitrary_types_allowed = True + + +def format_resource(quantity: float, unit: ResourceConstraintUnit) -> str: + """ + Get formatted string for a given quantity and unit (e.g. 1 second, 20 + seconds) + """ + if unit != ResourceConstraintUnit.TURNS: + quantity = round(quantity, 1) + if quantity == 1: + return f"{quantity} {unit.value.rstrip('s')}" + else: + return f"{quantity} {unit.value}" + + +class GCResource: + """ + Resource constraints for the GuidedConversation agent. This class is used to + keep track of the resource constraints. If resource_constraint is None, then + the agent can continue indefinitely. This also means that no agenda will be + created for the conversation. + + Args: + resource_constraint (ResourceConstraint | None): The resource constraint + for the conversation. + + initial_seconds_per_turn (int): The initial number of seconds per turn. + Defaults to 120 seconds. + """ + + def __init__( + self, + resource_constraint: ResourceConstraint | None, + initial_seconds_per_turn: int = 120, + ): + self.resource_constraint: ResourceConstraint | None = resource_constraint + self.initial_seconds_per_turn: int = initial_seconds_per_turn + + self.turn_number: int = 0 + + if resource_constraint is not None: + self.elapsed_units = 0 + self.remaining_units = resource_constraint.quantity + else: + self.elapsed_units = 0 + self.remaining_units = 0 + + def start_resource(self) -> None: + """To be called at the start of a conversation turn""" + if self.resource_constraint is not None and ( + self.resource_constraint.unit == ResourceConstraintUnit.SECONDS + or self.resource_constraint.unit == ResourceConstraintUnit.MINUTES + ): + self.start_time = time.time() + + def increment_resource(self) -> None: + """Increment the resource counter by one turn.""" + if self.resource_constraint is not None: + match self.resource_constraint.unit: + case ResourceConstraintUnit.SECONDS: + self.elapsed_units += time.time() - self.start_time + self.remaining_units = self.resource_constraint.quantity - self.elapsed_units + case ResourceConstraintUnit.MINUTES: + self.elapsed_units += (time.time() - self.start_time) / 60 + self.remaining_units = self.resource_constraint.quantity - self.elapsed_units + case ResourceConstraintUnit.TURNS: + self.elapsed_units += 1 + self.remaining_units -= 1 + case _: + raise ValueError("Invalid resource unit provided.") + self.turn_number += 1 + + def get_resource_mode(self) -> ResourceConstraintMode | None: + """ + Get the mode of the resource constraint. + """ + if self.resource_constraint is None: + return None + return self.resource_constraint.mode + + def get_elapsed_turns(self, formatted_repr: bool = False) -> str | int: + """ + Get the number of elapsed turns. + + Args: + formatted_repr (bool): If true, return a formatted string + representation of the elapsed turns. If false, return an integer. + Defaults to False. + + Returns: + str | int: The description/number of elapsed turns. + """ + if formatted_repr: + return format_resource(self.turn_number, ResourceConstraintUnit.TURNS) + else: + return self.turn_number + + def estimate_remaining_turns_formatted(self) -> str: + """ + Get the number of remaining turns in a formatted string. + """ + return format_resource(self.estimate_remaining_turns(), ResourceConstraintUnit.TURNS) + + def estimate_remaining_turns(self) -> int: + """ + Estimate the remaining turns based on the resource constraint, thereby + translating certain resource units (e.g. seconds, minutes) into turns. + """ + if self.resource_constraint is None: + logger.error( + "Resource constraint is not set, so turns cannot be estimated using function estimate_remaining_turns" + ) + raise ValueError( + "Resource constraint is not set. Do not try to call this method without a resource constraint." + ) + + match self.resource_constraint.unit: + case ResourceConstraintUnit.MINUTES: + if self.turn_number == 0: + time_per_turn = self.initial_seconds_per_turn + else: + time_per_turn = (self.elapsed_units * 60) / self.turn_number + time_per_turn /= 60 + remaining_turns = self.remaining_units / time_per_turn + if remaining_turns < 1: + return math.ceil(remaining_turns) + else: + return math.floor(remaining_turns) + + case ResourceConstraintUnit.SECONDS: + if self.turn_number == 0: + time_per_turn = self.initial_seconds_per_turn + else: + time_per_turn = self.elapsed_units / self.turn_number + remaining_turns = self.remaining_units / time_per_turn + if remaining_turns < 1: + return math.ceil(remaining_turns) + else: + return math.floor(remaining_turns) + + case ResourceConstraintUnit.TURNS: + return int(self.resource_constraint.quantity - self.turn_number) + + case _: + raise ValueError("Invalid resource unit provided.") + + def get_resource_instructions(self) -> str: + """Get the resource instructions for the conversation. + + Assumes we're always using turns as the resource unit. + + Returns: + str: the resource instructions + """ + if self.resource_constraint is None: + return "" + + formatted_elapsed_resource = format_resource( + self.elapsed_units, ResourceConstraintUnit.TURNS + ) + formatted_remaining_resource = format_resource( + self.remaining_units, ResourceConstraintUnit.TURNS + ) + + # if the resource quantity is anything other than 1, the resource unit should be plural (e.g. "minutes" instead of "minute") + is_plural_elapsed = self.elapsed_units != 1 + is_plural_remaining = self.remaining_units != 1 + + if self.elapsed_units > 0: + resource_instructions = f"So far, {formatted_elapsed_resource} {'have' if is_plural_elapsed else 'has'} elapsed since the conversation began. " + else: + resource_instructions = "" + + if self.resource_constraint.mode == ResourceConstraintMode.EXACT: + exact_mode_instructions = ( + f"There {'are' if is_plural_remaining else 'is'} {formatted_remaining_resource} " + "remaining (including this one) - the conversation will automatically terminate " + "when 0 turns are left. You should continue the conversation until it is " + "automatically terminated. This means you should NOT preemptively end the " + 'conversation, either explicitly (by selecting the "End conversation" action) ' + "or implicitly (e.g. by telling the user that you have all required information " + "and they should wait for the next step). Your goal is not to maximize efficiency " + "(i.e. complete the artifact as quickly as possible then end the conversation), " + "but rather to make the best use of ALL remaining turns available to you" + ) + + if is_plural_remaining: + resource_instructions += f"""{exact_mode_instructions}. This will require you to plan your actions carefully using the agenda: you want to avoid the situation where you have to pack too many topics into the final turns because you didn't account for them earlier, \ +or where you've rushed through the conversation and all fields are completed but there are still many turns left.""" + + # special instruction for the final turn (i.e. 1 remaining) in exact mode + else: + resource_instructions += f"""{exact_mode_instructions}, including this one. Therefore, you should use this turn to ask for any remaining information needed to complete the artifact, \ + or, if the artifact is already completed, continue to broaden/deepen the discussion in a way that's directly relevant to the artifact. Do NOT indicate to the user that the conversation is ending.""" + + elif self.resource_constraint.mode == ResourceConstraintMode.MAXIMUM: + resource_instructions += f"""You have a maximum of {formatted_remaining_resource} (including this one) left to complete the conversation. \ +You can decide to terminate the conversation at any point (including now), otherwise the conversation will automatically terminate when 0 turns are left. \ +You will need to plan your actions carefully using the agenda: you want to avoid the situation where you have to pack too many topics into the final turns because you didn't account for them earlier.""" + + else: + logger.error("Invalid resource mode provided.") + + return resource_instructions + + def to_json(self) -> dict: + return { + "turn_number": self.turn_number, + "remaining_units": self.remaining_units, + "elapsed_units": self.elapsed_units, + } + + @classmethod + def from_json( + cls, + json_data: dict, + ) -> "GCResource": + gc_resource = cls( + resource_constraint=None, + initial_seconds_per_turn=120, + ) + gc_resource.turn_number = json_data["turn_number"] + gc_resource.remaining_units = json_data["remaining_units"] + gc_resource.elapsed_units = json_data["elapsed_units"] + return gc_resource diff --git a/libraries/python/skills/skills/form-filler-skill/pyproject.toml b/libraries/python/skills/skills/form-filler-skill/pyproject.toml new file mode 100644 index 00000000..c7a4adc1 --- /dev/null +++ b/libraries/python/skills/skills/form-filler-skill/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "form-filler-skill" +version = "0.1.0" +description = "MADE:Exploration Form-filler skill" +authors = [{name="MADE:Explorers"}] +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "skill-library>=0.1.0", + "context>=0.1.0", + "events>=0.1.0", + "openai-client>=0.1.0", + # "guided-conversation>=0.1.0", +] + +[tool.uv] +package = true + +[tool.uv.sources] +skill-library = { path = "../../skill-library", editable= true } +context = { path = "../../../context", editable = true } +events = { path = "../../../events", editable = true } +openai-client = { path = "../../../openai-client", editable = true } +# guided-conversation = { path = "../../../guided-conversation", editable = true } + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/libraries/python/chat-driver/uv.lock b/libraries/python/skills/skills/form-filler-skill/uv.lock similarity index 92% rename from libraries/python/chat-driver/uv.lock rename to libraries/python/skills/skills/form-filler-skill/uv.lock index d185b278..cfb79cfc 100644 --- a/libraries/python/chat-driver/uv.lock +++ b/libraries/python/skills/skills/form-filler-skill/uv.lock @@ -122,6 +122,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262 }, ] +[[package]] +name = "assistant-drive" +version = "0.1.0" +source = { editable = "../../../assistant-drive" } +dependencies = [ + { name = "context" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../../context" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.5.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -281,39 +306,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../context" }, - { name = "events", editable = "../events" }, - { name = "function-registry", editable = "../function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - [[package]] name = "click" version = "8.1.7" @@ -338,13 +330,13 @@ wheels = [ [[package]] name = "context" version = "0.1.0" -source = { editable = "../context" } +source = { editable = "../../../context" } dependencies = [ { name = "events" }, ] [package.metadata] -requires-dist = [{ name = "events", editable = "../events" }] +requires-dist = [{ name = "events", editable = "../../../events" }] [package.metadata.requires-dev] dev = [ @@ -425,7 +417,7 @@ wheels = [ [[package]] name = "events" version = "0.1.0" -source = { editable = "../events" } +source = { editable = "../../../events" } dependencies = [ { name = "pydantic" }, ] @@ -475,6 +467,25 @@ standard = [ { name = "uvicorn", extra = ["standard"] }, ] +[[package]] +name = "form-filler-skill" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "context" }, + { name = "events" }, + { name = "openai-client" }, + { name = "skill-library" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../../context" }, + { name = "events", editable = "../../../events" }, + { name = "openai-client", editable = "../../../openai-client" }, + { name = "skill-library", editable = "../../skill-library" }, +] + [[package]] name = "frozenlist" version = "1.5.0" @@ -529,40 +540,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -873,12 +850,12 @@ wheels = [ [[package]] name = "openai-client" version = "0.1.0" -source = { editable = "../openai-client" } +source = { editable = "../../../openai-client" } dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -891,11 +868,11 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../function-registry" }, + { name = "events", editable = "../../../events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, - { name = "semantic-workbench-assistant", editable = "../semantic-workbench-assistant" }, + { name = "semantic-workbench-assistant", editable = "../../../semantic-workbench-assistant" }, { name = "tiktoken", specifier = ">=0.7.0" }, ] @@ -1181,11 +1158,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.17" +version = "0.0.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/22/edea41c2d4a22e666c0c7db7acdcbf7bc8c1c1f7d3b3ca246ec982fec612/python_multipart-0.0.17.tar.gz", hash = "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538", size = 36452 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/59/cbb0dc4eb07c2676d964d7ef986314abd0b90ef2b683864a04c13590487d/python_multipart-0.0.16.tar.gz", hash = "sha256:8dee37b88dab9b59922ca173c35acb627cc12ec74019f5cd4578369c6df36554", size = 36404 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/fb/275137a799169392f1fa88fff2be92f16eee38e982720a8aaadefc4a36b2/python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d", size = 24453 }, + { url = "https://files.pythonhosted.org/packages/ff/ec/e85b99fda29a3e7643ee39419fdd478782dbcd61c11d846a9a8fc748fff2/python_multipart-0.0.16-py3-none-any.whl", hash = "sha256:c2759b7b976ef3937214dfb592446b59dfaa5f04682a076f78b117c94776d87a", size = 24426 }, ] [[package]] @@ -1306,21 +1283,21 @@ wheels = [ [[package]] name = "rich" -version = "13.9.4" +version = "13.9.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +sdist = { url = "https://files.pythonhosted.org/packages/d9/e9/cf9ef5245d835065e6673781dbd4b8911d352fb770d56cf0879cf11b7ee1/rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e", size = 222889 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, + { url = "https://files.pythonhosted.org/packages/9a/e2/10e9819cf4a20bd8ea2f5dabafc2e6bf4a78d6a0965daeb60a4b34d1c11f/rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283", size = 242157 }, ] [[package]] name = "semantic-workbench-api-model" version = "0.1.0" -source = { editable = "../semantic-workbench-api-model" } +source = { editable = "../../../semantic-workbench-api-model" } dependencies = [ { name = "asgi-correlation-id" }, { name = "fastapi", extra = ["standard"] }, @@ -1335,7 +1312,7 @@ requires-dist = [ [[package]] name = "semantic-workbench-assistant" version = "0.1.0" -source = { editable = "../semantic-workbench-assistant" } +source = { editable = "../../../semantic-workbench-assistant" } dependencies = [ { name = "asgi-correlation-id" }, { name = "backoff" }, @@ -1356,7 +1333,7 @@ requires-dist = [ { name = "pydantic-settings", specifier = ">=2.2.0" }, { name = "python-json-logger", specifier = ">=2.0.7" }, { name = "rich", specifier = ">=13.7.0" }, - { name = "semantic-workbench-api-model", editable = "../semantic-workbench-api-model" }, + { name = "semantic-workbench-api-model", editable = "../../../semantic-workbench-api-model" }, ] [package.metadata.requires-dev] @@ -1385,6 +1362,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] +[[package]] +name = "skill-library" +version = "0.1.0" +source = { editable = "../../skill-library" } +dependencies = [ + { name = "assistant-drive" }, + { name = "context" }, + { name = "events" }, + { name = "openai" }, + { name = "openai-client" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "tiktoken" }, +] + +[package.metadata] +requires-dist = [ + { name = "assistant-drive", editable = "../../../assistant-drive" }, + { name = "context", editable = "../../../context" }, + { name = "events", editable = "../../../events" }, + { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../../openai-client" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.3.4" }, + { name = "python-dotenv", specifier = ">=1.0.1" }, + { name = "requests", specifier = ">=2.32.0" }, + { name = "tiktoken", specifier = ">=0.7.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1620,62 +1635,62 @@ wheels = [ [[package]] name = "yarl" -version = "1.17.1" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/9c/9c0a9bfa683fc1be7fdcd9687635151544d992cccd48892dc5e0a5885a29/yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47", size = 178163 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/0f/ce6a2c8aab9946446fb27f1e28f0fd89ce84ae913ab18a92d18078a1c7ed/yarl-1.17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217", size = 140727 }, - { url = "https://files.pythonhosted.org/packages/9d/df/204f7a502bdc3973cd9fc29e7dfad18ae48b3acafdaaf1ae07c0f41025aa/yarl-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988", size = 93560 }, - { url = "https://files.pythonhosted.org/packages/a2/e1/f4d522ae0560c91a4ea31113a50f00f85083be885e1092fc6e74eb43cb1d/yarl-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75", size = 91497 }, - { url = "https://files.pythonhosted.org/packages/f1/82/783d97bf4a226f1a2e59b1966f2752244c2bf4dc89bc36f61d597b8e34e5/yarl-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca", size = 339446 }, - { url = "https://files.pythonhosted.org/packages/e5/ff/615600647048d81289c80907165de713fbc566d1e024789863a2f6563ba3/yarl-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74", size = 354616 }, - { url = "https://files.pythonhosted.org/packages/a5/04/bfb7adb452bd19dfe0c35354ffce8ebc3086e028e5f8270e409d17da5466/yarl-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f", size = 351801 }, - { url = "https://files.pythonhosted.org/packages/10/e0/efe21edacdc4a638ce911f8cabf1c77cac3f60e9819ba7d891b9ceb6e1d4/yarl-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d", size = 343381 }, - { url = "https://files.pythonhosted.org/packages/63/f9/7bc7e69857d6fc3920ecd173592f921d5701f4a0dd3f2ae293b386cfa3bf/yarl-1.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11", size = 337093 }, - { url = "https://files.pythonhosted.org/packages/93/52/99da61947466275ff17d7bc04b0ac31dfb7ec699bd8d8985dffc34c3a913/yarl-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0", size = 346619 }, - { url = "https://files.pythonhosted.org/packages/91/8a/8aaad86a35a16e485ba0e5de0d2ae55bf8dd0c9f1cccac12be4c91366b1d/yarl-1.17.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3", size = 344347 }, - { url = "https://files.pythonhosted.org/packages/af/b6/97f29f626b4a1768ffc4b9b489533612cfcb8905c90f745aade7b2eaf75e/yarl-1.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe", size = 350316 }, - { url = "https://files.pythonhosted.org/packages/d7/98/8e0e8b812479569bdc34d66dd3e2471176ca33be4ff5c272a01333c4b269/yarl-1.17.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860", size = 361336 }, - { url = "https://files.pythonhosted.org/packages/9e/d3/d1507efa0a85c25285f8eb51df9afa1ba1b6e446dda781d074d775b6a9af/yarl-1.17.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4", size = 365350 }, - { url = "https://files.pythonhosted.org/packages/22/ba/ee7f1830449c96bae6f33210b7d89e8aaf3079fbdaf78ac398e50a9da404/yarl-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4", size = 357689 }, - { url = "https://files.pythonhosted.org/packages/a0/85/321c563dc5afe1661108831b965c512d185c61785400f5606006507d2e18/yarl-1.17.1-cp311-cp311-win32.whl", hash = "sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7", size = 83635 }, - { url = "https://files.pythonhosted.org/packages/bc/da/543a32c00860588ff1235315b68f858cea30769099c32cd22b7bb266411b/yarl-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3", size = 90218 }, - { url = "https://files.pythonhosted.org/packages/5d/af/e25615c7920396219b943b9ff8b34636ae3e1ad30777649371317d7f05f8/yarl-1.17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61", size = 141839 }, - { url = "https://files.pythonhosted.org/packages/83/5e/363d9de3495c7c66592523f05d21576a811015579e0c87dd38c7b5788afd/yarl-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d", size = 94125 }, - { url = "https://files.pythonhosted.org/packages/e3/a2/b65447626227ebe36f18f63ac551790068bf42c69bb22dfa3ae986170728/yarl-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139", size = 92048 }, - { url = "https://files.pythonhosted.org/packages/a1/f5/2ef86458446f85cde10582054fd5113495ef8ce8477da35aaaf26d2970ef/yarl-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5", size = 331472 }, - { url = "https://files.pythonhosted.org/packages/f3/6b/1ba79758ba352cdf2ad4c20cab1b982dd369aa595bb0d7601fc89bf82bee/yarl-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac", size = 341260 }, - { url = "https://files.pythonhosted.org/packages/2d/41/4e07c2afca3f9ed3da5b0e38d43d0280d9b624a3d5c478c425e5ce17775c/yarl-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463", size = 340882 }, - { url = "https://files.pythonhosted.org/packages/c3/c0/cd8e94618983c1b811af082e1a7ad7764edb3a6af2bc6b468e0e686238ba/yarl-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147", size = 336648 }, - { url = "https://files.pythonhosted.org/packages/ac/fc/73ec4340d391ffbb8f34eb4c55429784ec9f5bd37973ce86d52d67135418/yarl-1.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7", size = 325019 }, - { url = "https://files.pythonhosted.org/packages/57/48/da3ebf418fc239d0a156b3bdec6b17a5446f8d2dea752299c6e47b143a85/yarl-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685", size = 342841 }, - { url = "https://files.pythonhosted.org/packages/5d/79/107272745a470a8167924e353a5312eb52b5a9bb58e22686adc46c94f7ec/yarl-1.17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172", size = 341433 }, - { url = "https://files.pythonhosted.org/packages/30/9c/6459668b3b8dcc11cd061fc53e12737e740fb6b1575b49c84cbffb387b3a/yarl-1.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7", size = 344927 }, - { url = "https://files.pythonhosted.org/packages/c5/0b/93a17ed733aca8164fc3a01cb7d47b3f08854ce4f957cce67a6afdb388a0/yarl-1.17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da", size = 355732 }, - { url = "https://files.pythonhosted.org/packages/9a/63/ead2ed6aec3c59397e135cadc66572330325a0c24cd353cd5c94f5e63463/yarl-1.17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c", size = 362123 }, - { url = "https://files.pythonhosted.org/packages/89/bf/f6b75b4c2fcf0e7bb56edc0ed74e33f37fac45dc40e5a52a3be66b02587a/yarl-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199", size = 356355 }, - { url = "https://files.pythonhosted.org/packages/45/1f/50a0257cd07eef65c8c65ad6a21f5fb230012d659e021aeb6ac8a7897bf6/yarl-1.17.1-cp312-cp312-win32.whl", hash = "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96", size = 83279 }, - { url = "https://files.pythonhosted.org/packages/bc/82/fafb2c1268d63d54ec08b3a254fbe51f4ef098211501df646026717abee3/yarl-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df", size = 89590 }, - { url = "https://files.pythonhosted.org/packages/06/1e/5a93e3743c20eefbc68bd89334d9c9f04f3f2334380f7bbf5e950f29511b/yarl-1.17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488", size = 139974 }, - { url = "https://files.pythonhosted.org/packages/a1/be/4e0f6919013c7c5eaea5c31811c551ccd599d2fc80aa3dd6962f1bbdcddd/yarl-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374", size = 93364 }, - { url = "https://files.pythonhosted.org/packages/73/f0/650f994bc491d0cb85df8bb45392780b90eab1e175f103a5edc61445ff67/yarl-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac", size = 91177 }, - { url = "https://files.pythonhosted.org/packages/f3/e8/9945ed555d14b43ede3ae8b1bd73e31068a694cad2b9d3cad0a28486c2eb/yarl-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170", size = 333086 }, - { url = "https://files.pythonhosted.org/packages/a6/c0/7d167e48e14d26639ca066825af8da7df1d2fcdba827e3fd6341aaf22a3b/yarl-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8", size = 343661 }, - { url = "https://files.pythonhosted.org/packages/fa/81/80a266517531d4e3553aecd141800dbf48d02e23ebd52909e63598a80134/yarl-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938", size = 345196 }, - { url = "https://files.pythonhosted.org/packages/b0/77/6adc482ba7f2dc6c0d9b3b492e7cd100edfac4cfc3849c7ffa26fd7beb1a/yarl-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e", size = 338743 }, - { url = "https://files.pythonhosted.org/packages/6d/cc/f0c4c0b92ff3ada517ffde2b127406c001504b225692216d969879ada89a/yarl-1.17.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556", size = 326719 }, - { url = "https://files.pythonhosted.org/packages/18/3b/7bfc80d3376b5fa162189993a87a5a6a58057f88315bd0ea00610055b57a/yarl-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67", size = 345826 }, - { url = "https://files.pythonhosted.org/packages/2e/66/cf0b0338107a5c370205c1a572432af08f36ca12ecce127f5b558398b4fd/yarl-1.17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8", size = 340335 }, - { url = "https://files.pythonhosted.org/packages/2f/52/b084b0eec0fd4d2490e1d33ace3320fad704c5f1f3deaa709f929d2d87fc/yarl-1.17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3", size = 345301 }, - { url = "https://files.pythonhosted.org/packages/ef/38/9e2036d948efd3bafcdb4976cb212166fded76615f0dfc6c1492c4ce4784/yarl-1.17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0", size = 354205 }, - { url = "https://files.pythonhosted.org/packages/81/c1/13dfe1e70b86811733316221c696580725ceb1c46d4e4db852807e134310/yarl-1.17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299", size = 360501 }, - { url = "https://files.pythonhosted.org/packages/91/87/756e05c74cd8bf9e71537df4a2cae7e8211a9ebe0d2350a3e26949e1e41c/yarl-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258", size = 359452 }, - { url = "https://files.pythonhosted.org/packages/06/b2/b2bb09c1e6d59e1c9b1b36a86caa473e22c3dbf26d1032c030e9bfb554dc/yarl-1.17.1-cp313-cp313-win32.whl", hash = "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2", size = 308904 }, - { url = "https://files.pythonhosted.org/packages/f3/27/f084d9a5668853c1f3b246620269b14ee871ef3c3cc4f3a1dd53645b68ec/yarl-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda", size = 314637 }, - { url = "https://files.pythonhosted.org/packages/52/ad/1fe7ff5f3e8869d4c5070f47b96bac2b4d15e67c100a8278d8e7876329fc/yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06", size = 44352 }, +sdist = { url = "https://files.pythonhosted.org/packages/55/8f/d2d546f8b674335fa7ef83cc5c1892294f3f516c570893e65a7ea8ed49c9/yarl-1.17.0.tar.gz", hash = "sha256:d3f13583f378930377e02002b4085a3d025b00402d5a80911726d43a67911cd9", size = 177249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/72/a455fd01d4d33c10d683c209f87af5962bae54b13f435a69747354b169b1/yarl-1.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19a4fe0279626c6295c5b0c8c2bb7228319d2e985883621a6e87b344062d8135", size = 140427 }, + { url = "https://files.pythonhosted.org/packages/ca/f6/8f2af9ad1ceab385660f90930433d41191b8647ad3946a67ea573333317f/yarl-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cadd0113f4db3c6b56868d6a19ca6286f5ccfa7bc08c27982cf92e5ed31b489a", size = 93259 }, + { url = "https://files.pythonhosted.org/packages/5d/c5/61036a97e6686de3a3b47ffd51f2db10f4eff895dfdc287f27f9acdc4097/yarl-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60d6693eef43215b1ccfb1df3f6eae8db30a9ff1e7989fb6b2a6f0b468930ee8", size = 91194 }, + { url = "https://files.pythonhosted.org/packages/0c/a0/fe9db41a1807da0f6f9cbc78243da3267258734c383ff911696f506cae49/yarl-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb8bf3843e1fa8cf3fe77813c512818e57368afab7ebe9ef02446fe1a10b492", size = 339165 }, + { url = "https://files.pythonhosted.org/packages/27/d5/d99e6e25e77ea26ac1d73630ad26ba29ec01ec7594c530cf045b150f7e1f/yarl-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2a5b35fd1d8d90443e061d0c8669ac7600eec5c14c4a51f619e9e105b136715", size = 354290 }, + { url = "https://files.pythonhosted.org/packages/5f/98/0c475389a172e096467ef44cb59d649fc4f44ac186689a70299cd2e03dea/yarl-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5bf17b32f392df20ab5c3a69d37b26d10efaa018b4f4e5643c7520d8eee7ac7", size = 351486 }, + { url = "https://files.pythonhosted.org/packages/b2/0d/8ecf4604cf62abd8d4aa30dd927466b095f263ee708aed2e576f9f6c6ac8/yarl-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f51b529b958cd06e78158ff297a8bf57b4021243c179ee03695b5dbf9cb6e1", size = 343091 }, + { url = "https://files.pythonhosted.org/packages/c8/11/e0978e6e2f312c4ac5d441634df8374d25afa17164a6a5caed65f2071ce1/yarl-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fcaa06bf788e19f913d315d9c99a69e196a40277dc2c23741a1d08c93f4d430", size = 336785 }, + { url = "https://files.pythonhosted.org/packages/35/26/ecfebb253652b2446082e5072bf347dc2663a176f1a7f96830fb3f2ddb37/yarl-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:32f3ee19ff0f18a7a522d44e869e1ebc8218ad3ae4ebb7020445f59b4bbe5897", size = 346317 }, + { url = "https://files.pythonhosted.org/packages/4f/d7/bec0e8ab6788824a21b4d2a467ebd491c034bf5a61aae9f91bac3225cd0f/yarl-1.17.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a4fb69a81ae2ec2b609574ae35420cf5647d227e4d0475c16aa861dd24e840b0", size = 344050 }, + { url = "https://files.pythonhosted.org/packages/5d/cd/a3d7496963fa6fda90987efc6c6d63e17035a15607a7ba432b3658ee7c4a/yarl-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7bacc8b77670322132a1b2522c50a1f62991e2f95591977455fd9a398b4e678d", size = 350009 }, + { url = "https://files.pythonhosted.org/packages/4c/11/e32119eba4f1b2a888d653348571ec835fda93da45255d0d4e0fd557ae75/yarl-1.17.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:437bf6eb47a2d20baaf7f6739895cb049e56896a5ffdea61a4b25da781966e8b", size = 361038 }, + { url = "https://files.pythonhosted.org/packages/b2/3f/868044fff54c060cade272a54baaf57a155537ac79424312c6c0a3c0ff17/yarl-1.17.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30534a03c87484092080e3b6e789140bd277e40f453358900ad1f0f2e61fc8ec", size = 365043 }, + { url = "https://files.pythonhosted.org/packages/6f/63/99b77939e7a6b8dbb638fb7b6c6ecea4a730ccd7bdda5b621df9ff5bbbc6/yarl-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b30df4ff98703649915144be6f0df3b16fd4870ac38a09c56d5d9e54ff2d5f96", size = 357382 }, + { url = "https://files.pythonhosted.org/packages/b8/cc/48b49f45e4fc5fbb7538a6b513f0a4ae7377c44568e375fca65f270f03d7/yarl-1.17.0-cp311-cp311-win32.whl", hash = "sha256:263b487246858e874ab53e148e2a9a0de8465341b607678106829a81d81418c6", size = 83336 }, + { url = "https://files.pythonhosted.org/packages/ae/60/2ac590d83bb8aa5b8cc3d7f9c47d532d89fb06c3ffa2c4d4fc8d6935aded/yarl-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:07055a9e8b647a362e7d4810fe99d8f98421575e7d2eede32e008c89a65a17bd", size = 89919 }, + { url = "https://files.pythonhosted.org/packages/58/30/3d1b3eea23b9d1764c3d6a6bc22a12336bc91c748475dd1ea79f63a72bf1/yarl-1.17.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84095ab25ba69a8fa3fb4936e14df631b8a71193fe18bd38be7ecbe34d0f5512", size = 141535 }, + { url = "https://files.pythonhosted.org/packages/aa/0d/178955afc7b6b17f7a693878da366ad4dbf2adfee84cbb76640755115191/yarl-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02608fb3f6df87039212fc746017455ccc2a5fc96555ee247c45d1e9f21f1d7b", size = 93821 }, + { url = "https://files.pythonhosted.org/packages/d1/b3/808461c3c3d4c32ff8783364a8673bd785ce887b7421e0ea8d758357d874/yarl-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13468d291fe8c12162b7cf2cdb406fe85881c53c9e03053ecb8c5d3523822cd9", size = 91750 }, + { url = "https://files.pythonhosted.org/packages/95/8b/572f96dd61de8f8b82caf18254573707d526715ad38fd83c47663f2b3c28/yarl-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8da3f8f368fb7e2f052fded06d5672260c50b5472c956a5f1bd7bf474ae504ab", size = 331165 }, + { url = "https://files.pythonhosted.org/packages/4d/f6/8870c4beb0a120d381e7a62f6c1e6a590d929e94de135802ecdb042caffa/yarl-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec0507ab6523980bed050137007c76883d941b519aca0e26d4c1ec1f297dd646", size = 340972 }, + { url = "https://files.pythonhosted.org/packages/cb/08/97a6ccb59df29bbedb560491bc74f9f946dbf074bec1b61f942c29d2bc32/yarl-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08fc76df7fd8360e9ff30e6ccc3ee85b8dbd6ed5d3a295e6ec62bcae7601b932", size = 340557 }, + { url = "https://files.pythonhosted.org/packages/5a/f4/52be40fc0a8811a18a2b2ae99c6233e769fe391b52fae95a23a4db45e82c/yarl-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d522f390686acb6bab2b917dd9ca06740c5080cd2eaa5aef8827b97e967319d", size = 336362 }, + { url = "https://files.pythonhosted.org/packages/0a/25/b95d3c0130c65d2118b3b58d644261a3cd4571a317e5b46dcb2a44d096e2/yarl-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:147c527a80bb45b3dcd6e63401af8ac574125d8d120e6afe9901049286ff64ef", size = 324716 }, + { url = "https://files.pythonhosted.org/packages/ab/8a/b4d020a2b83bcab78d9cf094ed30cd08f966a7ce900abdbc3d57e34d1a4b/yarl-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:24cf43bcd17a0a1f72284e47774f9c60e0bf0d2484d5851f4ddf24ded49f33c6", size = 342539 }, + { url = "https://files.pythonhosted.org/packages/e9/e5/29959b19f9267dde6d80d9576bd95d9ed9463693a7c7e5408cd33bf66b18/yarl-1.17.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c28a44b9e0fba49c3857360e7ad1473fc18bc7f6659ca08ed4f4f2b9a52c75fa", size = 341129 }, + { url = "https://files.pythonhosted.org/packages/0a/b2/e5bb6f8909f96179b2982b6d4f44e3700b319eebbacf3f88adc75b2ae4e9/yarl-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:350cacb2d589bc07d230eb995d88fcc646caad50a71ed2d86df533a465a4e6e1", size = 344626 }, + { url = "https://files.pythonhosted.org/packages/86/6a/324d0b022032380ea8c378282d5e84e3d1535565489472518e80b8734f1f/yarl-1.17.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fd1ab1373274dea1c6448aee420d7b38af163b5c4732057cd7ee9f5454efc8b1", size = 355409 }, + { url = "https://files.pythonhosted.org/packages/20/f7/e2440d94826723f8bfd194a62ee014974ec416c16f953aa27c23e3ed3128/yarl-1.17.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4934e0f96dadc567edc76d9c08181633c89c908ab5a3b8f698560124167d9488", size = 361845 }, + { url = "https://files.pythonhosted.org/packages/d7/69/757dc8bb7a9e543b319e200c8c6ed30fbf7e7155736c609e2c140d0bb719/yarl-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8d0a278170d75c88e435a1ce76557af6758bfebc338435b2eba959df2552163e", size = 356050 }, + { url = "https://files.pythonhosted.org/packages/2c/3a/c563287d638200be202d46c03698079d85993b7c68f1488451546e60999b/yarl-1.17.0-cp312-cp312-win32.whl", hash = "sha256:61584f33196575a08785bb56db6b453682c88f009cd9c6f338a10f6737ce419f", size = 82982 }, + { url = "https://files.pythonhosted.org/packages/9a/cb/07a4084b90e7761749c56a5338c34366765051e9838eb669e449f012fdb2/yarl-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:9987a439ad33a7712bd5bbd073f09ad10d38640425fa498ecc99d8aa064f8fc4", size = 89294 }, + { url = "https://files.pythonhosted.org/packages/6c/4d/9285cd4d13a1bb521350656f89a09b6d44e4e167d4329246a01dc76a2128/yarl-1.17.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8deda7b8eb15a52db94c2014acdc7bdd14cb59ec4b82ac65d2ad16dc234a109e", size = 139677 }, + { url = "https://files.pythonhosted.org/packages/25/c9/eec62c4b4bb1151be548c378c06d3c7282aa70b027f0b26d24c6dde55106/yarl-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56294218b348dcbd3d7fce0ffd79dd0b6c356cb2a813a1181af730b7c40de9e7", size = 93066 }, + { url = "https://files.pythonhosted.org/packages/03/b0/ae2fc93595bf076bf568ed795a3f91ecf596975d9286aab62635340de1d7/yarl-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1fab91292f51c884b290ebec0b309a64a5318860ccda0c4940e740425a67b6b7", size = 90877 }, + { url = "https://files.pythonhosted.org/packages/3e/c2/8dd9c26534eaac304088674582e94d06d874e0b9c43ecf17d93d735eaf8a/yarl-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cf93fa61ff4d9c7d40482ce1a2c9916ca435e34a1b8451e17f295781ccc034f", size = 332747 }, + { url = "https://files.pythonhosted.org/packages/43/95/130310a39e90d99cf5894a4ea6bee147f133db3423e4d88bf6f2baba4ee4/yarl-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:261be774a0d71908c8830c33bacc89eef15c198433a8cc73767c10eeeb35a7d0", size = 343341 }, + { url = "https://files.pythonhosted.org/packages/e1/59/995a99e510f74d39c849157407d8d3e683b5b3d3d830f28de6dfca2c7f60/yarl-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deec9693b67f6af856a733b8a3e465553ef09e5e8ead792f52c25b699b8f9e6e", size = 344880 }, + { url = "https://files.pythonhosted.org/packages/78/41/520458d62a79b6115f035d63f6dec7c70ebfc19c50875cd0b9c3d63bd66f/yarl-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c804b07622ba50a765ca7fb8145512836ab65956de01307541def869e4a456c9", size = 338438 }, + { url = "https://files.pythonhosted.org/packages/b1/90/878e20cc8f54206407d035f17ccd567c75ed2bf77fb9c137c2977e58baf4/yarl-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d013a7c9574e98c14831a8f22d27277688ec3b2741d0188ac01a910b009987a", size = 326415 }, + { url = "https://files.pythonhosted.org/packages/0a/2e/709c8339cd5a0b8fb3e7474428165293feec85d77c642b95b0d7be7bda9c/yarl-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e2cfcba719bd494c7413dcf0caafb51772dec168c7c946e094f710d6aa70494e", size = 345526 }, + { url = "https://files.pythonhosted.org/packages/62/5e/90c60a9ac1b3f254b52e542674024160b90e0e547014f0d2a3025c789796/yarl-1.17.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c068aba9fc5b94dfae8ea1cedcbf3041cd4c64644021362ffb750f79837e881f", size = 340048 }, + { url = "https://files.pythonhosted.org/packages/ae/1f/2d086911313e4db00b28f5d105d64823dbcd4a78efcbba70bd58ffc72e20/yarl-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3616df510ffac0df3c9fa851a40b76087c6c89cbcea2de33a835fc80f9faac24", size = 344999 }, + { url = "https://files.pythonhosted.org/packages/da/f7/8670ff0427f82db0ec25f4f7e62f5111cc76d79b05a2fe9631155cd0f742/yarl-1.17.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:755d6176b442fba9928a4df787591a6a3d62d4969f05c406cad83d296c5d4e05", size = 353920 }, + { url = "https://files.pythonhosted.org/packages/68/b8/1f5a2fdecee03c23b4b5c9d394342709ed04e15bead1d3c7bee53854a61b/yarl-1.17.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c18f6e708d1cf9ff5b1af026e697ac73bea9cb70ee26a2b045b112548579bed2", size = 360209 }, + { url = "https://files.pythonhosted.org/packages/2b/95/d2e538a544c75131836b5e93975fa677932f0cbacbe4d7a4adb80caba967/yarl-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b937c216b6dee8b858c6afea958de03c5ff28406257d22b55c24962a2baf6fd", size = 359149 }, + { url = "https://files.pythonhosted.org/packages/93/c7/c7f954200ebae213f0b76b072dcd3c37b39a42f4cf3d80a30d580bcedef7/yarl-1.17.0-cp313-cp313-win32.whl", hash = "sha256:d0131b14cb545c1a7bd98f4565a3e9bdf25a1bd65c83fc156ee5d8a8499ec4a3", size = 308608 }, + { url = "https://files.pythonhosted.org/packages/c7/cc/57117f63f27668e87e3ea9ce9fecab7331f0a30b72690211a2857b5db9f5/yarl-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:01c96efa4313c01329e88b7e9e9e1b2fc671580270ddefdd41129fa8d0db7696", size = 314345 }, + { url = "https://files.pythonhosted.org/packages/93/86/f1305e1ab1d6dc27d245ffc83d18d88f2bebf6c6488725ee82dffb3eda7a/yarl-1.17.0-py3-none-any.whl", hash = "sha256:62dd42bb0e49423f4dd58836a04fcf09c80237836796025211bbe913f1524993", size = 44053 }, ] diff --git a/libraries/python/skills/skills/posix-skill/posix_skill/posix_skill.py b/libraries/python/skills/skills/posix-skill/posix_skill/posix_skill.py index 718c1500..571bfb51 100644 --- a/libraries/python/skills/skills/posix-skill/posix_skill/posix_skill.py +++ b/libraries/python/skills/skills/posix-skill/posix_skill/posix_skill.py @@ -1,8 +1,9 @@ from pathlib import Path -from skill_library import Skill, InstructionRoutine, RoutineTypes -from chat_driver import ChatDriverConfig + +from openai_client.chat_driver import ChatDriverConfig +from skill_library import InstructionRoutine, RoutineTypes, Skill + from .sandbox_shell import SandboxShell -from context import Context NAME = "posix" CLASS_NAME = "PosixSkill" @@ -14,12 +15,11 @@ class PosixSkill(Skill): def __init__( self, - context: Context, sandbox_dir: Path, chat_driver_config: ChatDriverConfig, mount_dir: str = "/mnt/data", ) -> None: - self.shell = SandboxShell(sandbox_dir / context.session_id, mount_dir) + self.shell = SandboxShell(sandbox_dir, mount_dir) # Put all functions in a group. We are going to use all these as (1) # skill actions, but also as (2) chat functions and (3) chat commands. @@ -43,9 +43,8 @@ def __init__( self.make_home_dir_routine(), ] - # Configure the skill's chat driver. + # Re-configure the skill's chat driver. chat_driver_config.instructions = INSTRUCTIONS - chat_driver_config.context = context chat_driver_config.commands = functions chat_driver_config.functions = functions @@ -53,7 +52,6 @@ def __init__( super().__init__( name=NAME, description=DESCRIPTION, - context=context, chat_driver_config=chat_driver_config, skill_actions=functions, routines=routines, @@ -78,13 +76,14 @@ def make_home_dir_routine(self) -> InstructionRoutine: "mkdir Pictures\n" "mkdir Videos\n" ), + skill=self, ) ################################## # Actions ################################## - def cd(self, context: Context, directory: str) -> str: + def cd(self, directory: str) -> str: """ Change the current working directory. """ @@ -94,60 +93,60 @@ def cd(self, context: Context, directory: str) -> str: except FileNotFoundError: return f"Directory {directory} not found." - def ls(self, context: Context, path: str = ".") -> list[str]: + def ls(self, path: str = ".") -> list[str]: """ List directory contents. """ return self.shell.ls(path) - def touch(self, context: Context, filename: str) -> str: + def touch(self, filename: str) -> str: """ Create an empty file. """ self.shell.touch(filename) return f"Created file {filename}." - def mkdir(self, context: Context, dirname: str) -> str: + def mkdir(self, dirname: str) -> str: """ Create a new directory. """ self.shell.mkdir(dirname) return f"Created directory {dirname}." - def mv(self, context: Context, src: str, dest: str) -> str: + def mv(self, src: str, dest: str) -> str: """ Move a file or directory. """ self.shell.mv(src, dest) return f"Moved {src} to {dest}." - def rm(self, context: Context, path: str) -> str: + def rm(self, path: str) -> str: """ Remove a file or directory. """ self.shell.rm(path) return f"Removed {path}." - def pwd(self, context: Context) -> str: + def pwd(self) -> str: """ Return the current directory. """ return self.shell.pwd() - def run_command(self, context: Context, command: str) -> str: + def run_command(self, command: str) -> str: """ Run a shell command in the current directory. """ stdout, stderr = self.shell.run_command(command) return f"Command output:\n{stdout}\nCommand errors:\n{stderr}" - def read_file(self, context: Context, filename: str) -> str: + def read_file(self, filename: str) -> str: """ Read the contents of a file. """ return self.shell.read_file(filename) - def write_file(self, context: Context, filename: str, content: str) -> str: + def write_file(self, filename: str, content: str) -> str: """ Write content to a file. """ diff --git a/libraries/python/skills/skills/posix-skill/pyproject.toml b/libraries/python/skills/skills/posix-skill/pyproject.toml index b72e1bb0..9729f258 100644 --- a/libraries/python/skills/skills/posix-skill/pyproject.toml +++ b/libraries/python/skills/skills/posix-skill/pyproject.toml @@ -6,10 +6,10 @@ authors = [{name="MADE:Explorers"}] readme = "README.md" requires-python = ">=3.11" dependencies = [ - "skill-library>=0.1.0", - "chat-driver>=0.1.0", "context>=0.1.0", "events>=0.1.0", + "openai-client>=0.1.0", + "skill-library>=0.1.0", ] [tool.uv] @@ -17,9 +17,10 @@ package = true [tool.uv.sources] skill-library = { path = "../../skill-library", editable= true } -chat-driver = { path = "../../../chat-driver", editable = true } context = { path = "../../../context", editable = true } events = { path = "../../../events", editable = true } +openai-client = { path = "../../../openai-client", editable = true } + [build-system] requires = ["hatchling"] diff --git a/libraries/python/skills/skills/posix-skill/uv.lock b/libraries/python/skills/skills/posix-skill/uv.lock index ef9776c5..854eeaec 100644 --- a/libraries/python/skills/skills/posix-skill/uv.lock +++ b/libraries/python/skills/skills/posix-skill/uv.lock @@ -122,6 +122,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262 }, ] +[[package]] +name = "assistant-drive" +version = "0.1.0" +source = { editable = "../../../assistant-drive" } +dependencies = [ + { name = "context" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../../context" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.5.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -281,39 +306,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "../../../chat-driver" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../context" }, - { name = "events", editable = "../../../events" }, - { name = "function-registry", editable = "../../../function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../../../openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - [[package]] name = "click" version = "8.1.7" @@ -529,40 +521,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../../function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -878,7 +836,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -891,7 +849,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../../function-registry" }, + { name = "events", editable = "../../../events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, @@ -977,17 +935,17 @@ name = "posix-skill" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../../chat-driver" }, { name = "context", editable = "../../../context" }, { name = "events", editable = "../../../events" }, + { name = "openai-client", editable = "../../../openai-client" }, { name = "skill-library", editable = "../../skill-library" }, ] @@ -1409,11 +1367,11 @@ name = "skill-library" version = "0.1.0" source = { editable = "../../skill-library" } dependencies = [ - { name = "chat-driver" }, + { name = "assistant-drive" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "openai" }, + { name = "openai-client" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -1423,11 +1381,11 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../../chat-driver" }, + { name = "assistant-drive", editable = "../../../assistant-drive" }, { name = "context", editable = "../../../context" }, { name = "events", editable = "../../../events" }, - { name = "function-registry", editable = "../../../function-registry" }, { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../../openai-client" }, { name = "pydantic", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.3.4" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1435,6 +1393,13 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "sniffio" version = "1.3.1" diff --git a/libraries/python/skills/skills/prospector-skill/prospector_skill/skill.py b/libraries/python/skills/skills/prospector-skill/prospector_skill/skill.py index cc77eac9..13046820 100644 --- a/libraries/python/skills/skills/prospector-skill/prospector_skill/skill.py +++ b/libraries/python/skills/skills/prospector-skill/prospector_skill/skill.py @@ -1,5 +1,5 @@ -from chat_driver import ChatDriverConfig -from context import Context +from context import ContextProtocol +from openai_client.chat_driver import ChatDriverConfig from skill_library import InstructionRoutine, RoutineTypes, Skill NAME = "prospector" @@ -12,7 +12,6 @@ class ProspectorSkill(Skill): def __init__( self, - context: Context, chat_driver_config: ChatDriverConfig, ) -> None: # Add some actions. @@ -32,14 +31,12 @@ def __init__( # Configure the chat driver (if using). Register all the supplied actions to it as either # commands, functions, or both. chat_driver_config.instructions = INSTRUCTIONS - chat_driver_config.context = context chat_driver_config.commands = actions chat_driver_config.functions = actions super().__init__( name=NAME, description=DESCRIPTION, - context=context, chat_driver_config=chat_driver_config, skill_actions=actions, routines=routines, @@ -56,7 +53,8 @@ def draft_grant_proposal_routine(self) -> InstructionRoutine: return InstructionRoutine( "draft_grant_proposal", # name of routine "Draft a grant proposal.", # description of routine - routine=("gather_information_action\n" "create_draft_action\n"), + routine=("gather_information_action\ncreate_draft_action\n"), + skill=self, ) def example_routine(self) -> InstructionRoutine: @@ -66,32 +64,33 @@ def example_routine(self) -> InstructionRoutine: return InstructionRoutine( "template_example", # name of routine "Description of what the routine does.", - routine=("template_example_action\n" "template_example_with_parameters_action bar\n"), + routine=("template_example_action\ntemplate_example_with_parameters_action bar\n"), + skill=self, ) ################################## # Actions ################################## - def gather_information_action(self, context: Context) -> None: + def gather_information_action(self, context: ContextProtocol) -> None: """ Update this action description. """ pass - def create_draft_action(self, context: Context) -> None: + def create_draft_action(self, context: ContextProtocol) -> None: """ Update this action description. """ pass - def example_action(self, context: Context) -> None: + def example_action(self, context: ContextProtocol) -> None: """ Update this action description. """ pass - def example_with_parameters_action(self, context: Context, foo: str) -> None: + def example_with_parameters_action(self, context: ContextProtocol, foo: str) -> None: """ Update this action description. """ diff --git a/libraries/python/skills/skills/prospector-skill/pyproject.toml b/libraries/python/skills/skills/prospector-skill/pyproject.toml index 344d128f..f33f3862 100644 --- a/libraries/python/skills/skills/prospector-skill/pyproject.toml +++ b/libraries/python/skills/skills/prospector-skill/pyproject.toml @@ -6,20 +6,21 @@ authors = [{name="MADE:Explorers"}] readme = "README.md" requires-python = ">=3.11" dependencies = [ - "skill-library>=0.1.0", - "chat-driver>=0.1.0", "context>=0.1.0", "events>=0.1.0", + "openai-client>=0.1.0", + "skill-library>=0.1.0", ] [tool.uv] package = true [tool.uv.sources] -skill-library = { path = "../../skill-library", editable = true } -chat-driver = { path = "../../../chat-driver", editable = true } context = { path = "../../../context", editable = true } events = { path = "../../../events", editable = true } +openai-client = { path = "../../../openai-client", editable = true } +skill-library = { path = "../../skill-library", editable = true } + [build-system] requires = ["hatchling"] diff --git a/libraries/python/skills/skills/prospector-skill/uv.lock b/libraries/python/skills/skills/prospector-skill/uv.lock index ade64af0..e79c712b 100644 --- a/libraries/python/skills/skills/prospector-skill/uv.lock +++ b/libraries/python/skills/skills/prospector-skill/uv.lock @@ -122,6 +122,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262 }, ] +[[package]] +name = "assistant-drive" +version = "0.1.0" +source = { editable = "../../../assistant-drive" } +dependencies = [ + { name = "context" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../../context" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.5.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -281,39 +306,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "../../../chat-driver" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../context" }, - { name = "events", editable = "../../../events" }, - { name = "function-registry", editable = "../../../function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../../../openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - [[package]] name = "click" version = "8.1.7" @@ -529,40 +521,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../../function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -878,7 +836,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -891,7 +849,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../../function-registry" }, + { name = "events", editable = "../../../events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, @@ -1034,17 +992,17 @@ name = "prospector-skill" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../../chat-driver" }, { name = "context", editable = "../../../context" }, { name = "events", editable = "../../../events" }, + { name = "openai-client", editable = "../../../openai-client" }, { name = "skill-library", editable = "../../skill-library" }, ] @@ -1409,11 +1367,11 @@ name = "skill-library" version = "0.1.0" source = { editable = "../../skill-library" } dependencies = [ - { name = "chat-driver" }, + { name = "assistant-drive" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "openai" }, + { name = "openai-client" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -1423,11 +1381,11 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../../chat-driver" }, + { name = "assistant-drive", editable = "../../../assistant-drive" }, { name = "context", editable = "../../../context" }, { name = "events", editable = "../../../events" }, - { name = "function-registry", editable = "../../../function-registry" }, { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../../openai-client" }, { name = "pydantic", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.3.4" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1435,6 +1393,13 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "sniffio" version = "1.3.1" diff --git a/libraries/python/skills/skills/skill-template/pyproject.toml b/libraries/python/skills/skills/skill-template/pyproject.toml index 42fdc2bb..4af2e4e6 100644 --- a/libraries/python/skills/skills/skill-template/pyproject.toml +++ b/libraries/python/skills/skills/skill-template/pyproject.toml @@ -5,20 +5,20 @@ description = "MADE:Exploration skill" readme = "README.md" requires-python = ">=3.11" dependencies = [ - "skill-library>=0.1.0", - "chat-driver>=0.1.0", "context>=0.1.0", "events>=0.1.0", + "openai-client>=0.1.0", + "skill-library>=0.1.0", ] [tool.uv] package = true [tool.uv.sources] -skill-library = { path = "../../skill-library", editable = true } -chat-driver = { path = "../../../chat-driver", editable = true } context = { path = "../../../context", editable = true } events = { path = "../../../events", editable = true } +skill-library = { path = "../../skill-library", editable = true } + [build-system] requires = ["hatchling"] diff --git a/libraries/python/skills/skills/skill-template/uv.lock b/libraries/python/skills/skills/skill-template/uv.lock index 177ca9c6..e5cadbc6 100644 --- a/libraries/python/skills/skills/skill-template/uv.lock +++ b/libraries/python/skills/skills/skill-template/uv.lock @@ -122,6 +122,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262 }, ] +[[package]] +name = "assistant-drive" +version = "0.1.0" +source = { editable = "../../../assistant-drive" } +dependencies = [ + { name = "context" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../../context" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.5.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -281,39 +306,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] -[[package]] -name = "chat-driver" -version = "0.1.0" -source = { editable = "../../../chat-driver" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "events" }, - { name = "function-registry" }, - { name = "openai" }, - { name = "openai-client" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../context" }, - { name = "events", editable = "../../../events" }, - { name = "function-registry", editable = "../../../function-registry" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "openai-client", editable = "../../../openai-client" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - [[package]] name = "click" version = "8.1.7" @@ -529,40 +521,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] -[[package]] -name = "function-registry" -version = "0.1.0" -source = { editable = "../../../function-registry" } -dependencies = [ - { name = "azure-identity" }, - { name = "context" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "tiktoken" }, -] - -[package.metadata] -requires-dist = [ - { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "context", editable = "../../../context" }, - { name = "openai", specifier = ">=1.16.1" }, - { name = "pydantic", specifier = ">=2.6.1" }, - { name = "pydantic-settings", specifier = ">=2.3.4" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.0" }, - { name = "tiktoken", specifier = ">=0.7.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=8.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.23.8" }, - { name = "pytest-repeat", specifier = ">=0.9.3" }, -] - [[package]] name = "h11" version = "0.14.0" @@ -878,7 +836,7 @@ dependencies = [ { name = "azure-ai-contentsafety" }, { name = "azure-core", extra = ["aio"] }, { name = "azure-identity" }, - { name = "function-registry" }, + { name = "events" }, { name = "openai" }, { name = "pillow" }, { name = "python-liquid" }, @@ -891,7 +849,7 @@ requires-dist = [ { name = "azure-ai-contentsafety", specifier = ">=1.0.0" }, { name = "azure-core", extras = ["aio"], specifier = ">=1.30.0" }, { name = "azure-identity", specifier = ">=1.17.1" }, - { name = "function-registry", editable = "../../../function-registry" }, + { name = "events", editable = "../../../events" }, { name = "openai", specifier = ">=1.3.9" }, { name = "pillow", specifier = ">=11.0.0" }, { name = "python-liquid", specifier = ">=1.12.1" }, @@ -1390,11 +1348,11 @@ name = "skill-library" version = "0.1.0" source = { editable = "../../skill-library" } dependencies = [ - { name = "chat-driver" }, + { name = "assistant-drive" }, { name = "context" }, { name = "events" }, - { name = "function-registry" }, { name = "openai" }, + { name = "openai-client" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-dotenv" }, @@ -1404,11 +1362,11 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../../chat-driver" }, + { name = "assistant-drive", editable = "../../../assistant-drive" }, { name = "context", editable = "../../../context" }, { name = "events", editable = "../../../events" }, - { name = "function-registry", editable = "../../../function-registry" }, { name = "openai", specifier = ">=1.16.1" }, + { name = "openai-client", editable = "../../../openai-client" }, { name = "pydantic", specifier = ">=2.6.1" }, { name = "pydantic-settings", specifier = ">=2.3.4" }, { name = "python-dotenv", specifier = ">=1.0.1" }, @@ -1416,6 +1374,13 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.1" }, + { name = "pytest-asyncio", specifier = ">=0.23.8" }, + { name = "pytest-repeat", specifier = ">=0.9.3" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1716,16 +1681,16 @@ name = "your-skill" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "chat-driver" }, { name = "context" }, { name = "events" }, + { name = "openai-client" }, { name = "skill-library" }, ] [package.metadata] requires-dist = [ - { name = "chat-driver", editable = "../../../chat-driver" }, { name = "context", editable = "../../../context" }, { name = "events", editable = "../../../events" }, + { name = "openai-client", specifier = ">=0.1.0" }, { name = "skill-library", editable = "../../skill-library" }, ] diff --git a/libraries/python/skills/skills/skill-template/your_skill/skill.py b/libraries/python/skills/skills/skill-template/your_skill/skill.py index cccdd4b8..95a22957 100644 --- a/libraries/python/skills/skills/skill-template/your_skill/skill.py +++ b/libraries/python/skills/skills/skill-template/your_skill/skill.py @@ -1,5 +1,5 @@ -from chat_driver import ChatDriverConfig -from context import Context +from context import ContextProtocol +from openai_client.chat_driver import ChatDriverConfig from skill_library import InstructionRoutine, RoutineTypes, Skill NAME = "your" @@ -12,7 +12,6 @@ class YourSkill(Skill): def __init__( self, - context: Context, chat_driver_config: ChatDriverConfig, ) -> None: # Add some actions. @@ -29,14 +28,12 @@ def __init__( # Configure the chat driver (if using). Register all the supplied actions to it as either # commands, functions, or both. chat_driver_config.instructions = INSTRUCTIONS - chat_driver_config.context = context chat_driver_config.commands = actions chat_driver_config.functions = actions super().__init__( name=NAME, description=DESCRIPTION, - context=context, chat_driver_config=chat_driver_config, skill_actions=actions, routines=routines, @@ -53,20 +50,21 @@ def example_routine(self) -> InstructionRoutine: return InstructionRoutine( "template_example", # name of routine "Description of what the routine does.", - routine=("template_example_action\n" "template_example_with_parameters_action bar\n"), + routine=("template_example_action\ntemplate_example_with_parameters_action bar\n"), + skill=self, ) ################################## # Actions ################################## - def example_action(self, context: Context) -> None: + def example_action(self, context: ContextProtocol) -> None: """ Update this action description. """ pass - def example_with_parameters_action(self, context: Context, foo: str) -> None: + def example_with_parameters_action(self, context: ContextProtocol, foo: str) -> None: """ Update this action description. """ diff --git a/semantic-workbench.code-workspace b/semantic-workbench.code-workspace index b01bfc92..aa838b42 100644 --- a/semantic-workbench.code-workspace +++ b/semantic-workbench.code-workspace @@ -75,10 +75,6 @@ "name": "libraries:assistant-extensions", "path": "libraries/python/assistant-extensions" }, - { - "name": "libraries:chat-driver", - "path": "libraries/python/chat-driver" - }, { "name": "libraries:content-safety", "path": "libraries/python/content-safety" @@ -91,10 +87,6 @@ "name": "libraries:events", "path": "libraries/python/events" }, - { - "name": "libraries:function-registry", - "path": "libraries/python/function-registry" - }, { "name": "libraries:guided-conversation", "path": "libraries/python/guided-conversation" @@ -115,6 +107,10 @@ "name": "libraries:skills:document-skill", "path": "libraries/python/skills/skills/document-skill" }, + { + "name": "libraries:skills:form-filler-skill", + "path": "libraries/python/skills/skills/form-filler-skill" + }, { "name": "libraries:skills:notebooks", "path": "libraries/python/skills/notebooks" @@ -151,4 +147,4 @@ "settings": { "markdown.validate.enabled": true } -} +} \ No newline at end of file