diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0646e36..4f0d414 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -57,7 +57,6 @@ "epivision.vscode-file-header", "esbenp.prettier-vscode", "github.vscode-github-actions", - "matt-rudge.auto-open-preview-panel", "ms-azuretools.vscode-docker", "ms-python.debugpy", "ms-python.python", diff --git a/.vscode/settings.json b/.vscode/settings.json index e7a9f44..8d1298b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,13 +23,15 @@ "**/.data/**", "**/__pycache__/**" ], - "python.analysis.fixAll": ["source.unusedImports"], + "python.analysis.fixAll": [ + "source.unusedImports" + ], "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.typeCheckingMode": "basic", "python.defaultInterpreterPath": "${workspaceFolder}/workbench-service/.venv", "python.languageServer": "Pylance", "python.testing.pytestEnabled": true, - "python.testing.cwd": "${workspaceFolder:v1}/workbench-service", + "python.testing.cwd": "${workspaceFolder}/workbench-service", "python.testing.pytestArgs": [], "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", @@ -56,7 +58,9 @@ "source.fixAll": "explicit" } }, - "css.lint.validProperties": ["composes"], + "css.lint.validProperties": [ + "composes" + ], "editor.defaultFormatter": "esbenp.prettier-vscode", "eslint.lintTask.enable": true, "editor.formatOnPaste": true, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd808a4..da4314e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,6 +99,8 @@ We use and recommend the following workflow: 8. Wait for feedback or approval of your changes from the code maintainers. 9. When area owners have signed off, and all checks are green, your PR will be merged. +For a detailed walkthrough of this workflow, including how to set up forks and manage your Git workflow, refer to the [Detailed Workflow Walkthrough](#detailed-workflow-walkthrough) section. + ### Adding Assistants We appreciate your interest in extending Semantic Workbench's functionality through @@ -120,3 +122,99 @@ and test runs must be clean. If the CI build fails for any reason, the PR issue will be updated with a link that can be used to determine the cause of the failure. + +### Detailed Workflow Walkthrough + +This detailed guide walks you through the process of contributing to our repository via forking, cloning, and managing your Git workflow. + +Start by forking the repository on GitHub. This creates a copy of the repository under your GitHub account. + +Clone your forked repository to your local machine: + +```bash +git clone https://github.com/YOUR_USERNAME/semanticworkbench.git +cd semanticworkbench +``` + +Add the original repository as an upstream remote: + +```bash +git remote add upstream https://github.com/microsoft/semanticworkbench.git +``` + +Check your remotes to ensure you have both `origin` and `upstream`: + +```bash +git remote -v +``` + +You should see something like this: + +``` +origin https://github.com/YOUR_USERNAME/semanticworkbench.git (fetch) +origin https://github.com/YOUR_USERNAME/semanticworkbench.git (push) +upstream https://github.com/microsoft/semanticworkbench.git (fetch) +upstream https://github.com/microsoft/semanticworkbench.git (push) +``` + +To keep your fork updated with the latest changes from upstream, configure your local `main` branch to track the upstream `main` branch: + +```bash +git branch -u upstream/main main +``` + +Alternatively, you can edit your `.git/config` file: + +```ini +[branch "main"] + remote = upstream + merge = refs/heads/main +``` + +Before starting a new feature or bug fix, ensure that your fork is up-to-date with the latest changes from upstream: + +```bash +git checkout main +git pull upstream main +``` + +Create a new branch for your feature or bug fix: + +```bash +git checkout -b feature-name +``` + +Make your changes in the codebase. Once you are satisfied, add and commit your changes: + +```bash +git add . +git commit -m "Description of your changes" +``` + +Push your changes to your fork: + +```bash +git push origin feature-name +``` + +Go to your fork on GitHub, and you should see a `Compare & pull request` button. Click it and submit your pull request (PR) against the original repository’s `main` branch. + +If there are changes in the main repository after you created your branch, sync them to your branch: + +```bash +git checkout main +git pull upstream main +git checkout feature-name +git rebase main +``` + +Once your PR is merged, you can delete your branch both locally and from GitHub. + +**Locally:** + +```bash +git branch -d feature-name +``` + +**On GitHub:** +Go to your fork and delete the branch from the `Branches` section. diff --git a/README.md b/README.md index 17f781a..697037c 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ When you submit a pull request, a CLA bot will automatically determine whether y a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. +Please see the detailed [contributing guide](CONTRIBUTING.md) for more information on how you can get involved. + This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/assistants/guided-conversation-assistant/assistant/agents/guided_conversation/config.py b/assistants/guided-conversation-assistant/assistant/agents/guided_conversation/config.py index 7ad76ce..aff4981 100644 --- a/assistants/guided-conversation-assistant/assistant/agents/guided_conversation/config.py +++ b/assistants/guided-conversation-assistant/assistant/agents/guided_conversation/config.py @@ -90,7 +90,7 @@ class GuidedConversationAgentConfigModel(BaseModel): description="A loose natural language description of the steps of the conversation", ), UISchema(widget="textarea", placeholder="[optional]"), - ] = config_defaults.conversation_flow + ] = config_defaults.conversation_flow.strip() context: Annotated[ str, @@ -99,7 +99,7 @@ class GuidedConversationAgentConfigModel(BaseModel): description="General background context for the conversation.", ), UISchema(widget="textarea", placeholder="[optional]"), - ] = config_defaults.context + ] = config_defaults.context.strip() class ResourceConstraint(ResourceConstraint): mode: Annotated[ diff --git a/assistants/guided-conversation-assistant/assistant/agents/guided_conversation/config_defaults.py b/assistants/guided-conversation-assistant/assistant/agents/guided_conversation/config_defaults.py index 1bab0fc..c938c9b 100644 --- a/assistants/guided-conversation-assistant/assistant/agents/guided_conversation/config_defaults.py +++ b/assistants/guided-conversation-assistant/assistant/agents/guided_conversation/config_defaults.py @@ -15,8 +15,10 @@ class ArtifactModel(BaseModel): initial_feedback: str = Field(description="Feedback on the student's final revised poem.") final_feedback: str = Field(description="Feedback on how the student was able to improve their poem.") inappropriate_behavior: list[str] = Field( - description="""List any inappropriate behavior the student attempted while chatting with you. \ -It is ok to leave this field Unanswered if there was none.""" + description=""" +List any inappropriate behavior the student attempted while chatting with you. +It is ok to leave this field Unanswered if there was none. +""" ) @@ -27,7 +29,8 @@ class ArtifactModel(BaseModel): ] # Conversation Flow (optional) - This defines in natural language the steps of the conversation. -conversation_flow = """1. Start by explaining interactively what an acrostic poem is. +conversation_flow = """ +1. Start by explaining interactively what an acrostic poem is. 2. Then give the following instructions for how to go ahead and write one: 1. Choose a word or phrase that will be the subject of your acrostic poem. 2. Write the letters of your chosen word or phrase vertically down the page. @@ -39,14 +42,17 @@ class ArtifactModel(BaseModel): Pizza parties on the weekend, Puppies we bend down to tend, Yelling yay when we win the game -4. Finally have the student write their own acrostic poem using the word or phrase of their choice. Encourage them to be creative and have fun with it. -After they write it, you should review it and give them feedback on what they did well and what they could improve on. -Have them revise their poem based on your feedback and then review it again. +4. Finally have the student write their own acrostic poem using the word or phrase of their choice. Encourage them +to be creative and have fun with it. After they write it, you should review it and give them feedback on what they +did well and what they could improve on. Have them revise their poem based on your feedback and then review it again. """ # Context (optional) - This is any additional information or the circumstances the agent is in that it should be aware of. # It can also include the high level goal of the conversation if needed. -context = """You are working 1 on 1 a 4th grade student who is chatting with you in the computer lab at school while being supervised by their teacher.""" +context = """ +You are working 1 on 1 a 4th grade student who is chatting with you in the computer lab at school while being +supervised by their teacher. +""" # Resource Constraints (optional) - This defines the constraints on the conversation such as time or turns. diff --git a/assistants/prospector-assistant/.vscode/settings.json b/assistants/prospector-assistant/.vscode/settings.json index 4df24a1..b86c171 100644 --- a/assistants/prospector-assistant/.vscode/settings.json +++ b/assistants/prospector-assistant/.vscode/settings.json @@ -59,6 +59,7 @@ "pydantic", "pyproject", "tiktoken", + "updown", "virtualenvs" ] } diff --git a/assistants/prospector-assistant/assistant/agents/artifact_agent.py b/assistants/prospector-assistant/assistant/agents/artifact_agent.py index 3a16c02..8719574 100644 --- a/assistants/prospector-assistant/assistant/agents/artifact_agent.py +++ b/assistants/prospector-assistant/assistant/agents/artifact_agent.py @@ -1,4 +1,3 @@ -import pathlib from pathlib import Path from typing import TYPE_CHECKING, Annotated, Literal, Union @@ -12,40 +11,21 @@ from semantic_workbench_assistant.config import UISchema from semantic_workbench_assistant.storage import read_model, write_model +from .. import helpers + if TYPE_CHECKING: from ..config import AssistantConfigModel - -# -# region Helpers -# - - -# helper for loading an include from a text file -def load_text_include(filename) -> str: - # get directory relative to this module - directory = pathlib.Path(__file__).parent.parent - - # get the file path for the prompt file - file_path = directory / "text_includes" / filename - - # read the prompt from the file - return file_path.read_text() - - -# endregion - - # # region Models # class ArtifactAgentConfigModel(BaseModel): - enable_artifacts: Annotated[ + enabled: Annotated[ bool, Field( - description=load_text_include("artifact_agent_enable_artifacts.md"), + description=helpers.load_text_include("artifact_agent_enabled.md"), ), UISchema(enable_markdown_in_description=True), ] = False @@ -283,7 +263,7 @@ async def get(self, context: ConversationContext) -> AssistantConversationInspec # get the configuration for the artifact agent config = await self.config_provider.get(context.assistant) - if not config.agents_config.artifact_agent.enable_artifacts: + if not config.agents_config.artifact_agent.enabled: return AssistantConversationInspectorStateDataModel( data={"content": "Artifacts are disabled in assistant configuration."} ) diff --git a/assistants/prospector-assistant/assistant/agents/attachment_agent.py b/assistants/prospector-assistant/assistant/agents/attachment_agent.py index e8b925d..2d9362b 100644 --- a/assistants/prospector-assistant/assistant/agents/attachment_agent.py +++ b/assistants/prospector-assistant/assistant/agents/attachment_agent.py @@ -1,11 +1,12 @@ import base64 import io import logging -from pathlib import Path from typing import Annotated, Any import docx2txt import pdfplumber +from assistant_drive import Drive, DriveConfig +from context import Context from openai.types import chat from pydantic import BaseModel, Field from semantic_workbench_api_model.workbench_model import File @@ -14,11 +15,6 @@ FileStorageContext, ) from semantic_workbench_assistant.config import UISchema -from semantic_workbench_assistant.storage import ( - read_model, - read_models_in_dir, - write_model, -) logger = logging.getLogger(__name__) @@ -84,15 +80,16 @@ async def create_or_update_attachment_from_file( content = await _file_to_str(context, file) # see if there is already an attachment with this filename - attachment = read_model(_get_attachment_storage_path(context, filename), Attachment) - if attachment: + try: + attachment = _get_attachment_drive(context).read_model(Attachment, filename) # if there is, update the content attachment.content = content - else: + except FileNotFoundError: # if there isn't, create a new attachment attachment = Attachment(filename=filename, content=content, metadata=metadata) - write_model(_get_attachment_storage_path(context, filename), attachment) + # write the attachment to the storage + _get_attachment_drive(context).write_model(attachment, filename) @staticmethod def delete_attachment_for_file(context: ConversationContext, file: File) -> None: @@ -101,7 +98,7 @@ def delete_attachment_for_file(context: ConversationContext, file: File) -> None """ filename = file.filename - _get_attachment_storage_path(context, filename).unlink(missing_ok=True) + _get_attachment_drive(context).delete(filename) @staticmethod def generate_attachment_messages( @@ -120,7 +117,7 @@ def generate_attachment_messages( """ # get all attachments and exit early if there are none - attachments = read_models_in_dir(_get_attachment_storage_path(context), Attachment) + attachments = _get_attachment_drive(context).read_models(Attachment) if not attachments: return [] @@ -233,14 +230,13 @@ def reduce_attachment_payload_from_content(value: Any) -> Any: # -def _get_attachment_storage_path(context: ConversationContext, filename: str | None = None) -> Path: +def _get_attachment_drive(context: ConversationContext) -> Drive: """ - Get the path where attachments are stored. + Get the Drive instance for the attachments. """ - path = FileStorageContext.get(context).directory / "attachments" - if filename: - path /= filename - return path + drive_context = Context(session_id=context.id) + drive_root = str(FileStorageContext.get(context).directory / "attachments") + return Drive(DriveConfig(context=drive_context, root=drive_root)) async def _raw_content_from_file(context: ConversationContext, file: File) -> bytes: diff --git a/assistants/prospector-assistant/assistant/agents/guided_conversation/config.py b/assistants/prospector-assistant/assistant/agents/guided_conversation/config.py new file mode 100644 index 0000000..d76deda --- /dev/null +++ b/assistants/prospector-assistant/assistant/agents/guided_conversation/config.py @@ -0,0 +1,147 @@ +import json +from typing import Annotated, Any, Dict, List, Type, get_type_hints + +from guided_conversation.utils.resources import ResourceConstraint, ResourceConstraintMode, ResourceConstraintUnit +from pydantic import BaseModel, Field, create_model +from pydantic_core import PydanticUndefinedType +from semantic_workbench_assistant.config import UISchema + +from ... import helpers +from . import draft_grant_proposal_config_defaults as config_defaults + +# +# region Helpers +# + + +def determine_type(type_str: str) -> Type: + type_mapping = {"str": str, "int": int, "float": float, "bool": bool, "list": List[Any], "dict": Dict[str, Any]} + return type_mapping.get(type_str, Any) + + +def create_pydantic_model_from_json(json_data: str) -> Type[BaseModel]: + data = json.loads(json_data) + + def create_fields(data: Dict[str, Any]) -> Dict[str, Any]: + fields = {} + for key, value in data.items(): + if value["type"] == "dict": + nested_model = create_pydantic_model_from_json(json.dumps(value["value"])) + fields[key] = (nested_model, Field(description=value["description"])) + else: + fields[key] = ( + determine_type(value["type"]), + Field(default=value["value"], description=value["description"]), + ) + return fields + + fields = create_fields(data) + return create_model("DynamicModel", **fields) + + +def pydantic_model_to_json(model: BaseModel) -> Dict[str, Any]: + def get_type_str(py_type: Any) -> str: + type_mapping = {str: "str", int: "int", float: "float", bool: "bool", list: "list", dict: "dict"} + return type_mapping.get(py_type, "any") + + json_dict = {} + for field_name, field in model.model_fields.items(): + field_type = get_type_hints(model)[field_name] + default_value = field.default if not isinstance(field.default, PydanticUndefinedType) else "" + json_dict[field_name] = { + "value": default_value, + "type": get_type_str(field_type), + "description": field.description or "", + } + return json_dict + + +# endregion + + +# +# region Models +# + + +class GuidedConversationAgentConfigModel(BaseModel): + enabled: Annotated[ + bool, + Field(description=helpers.load_text_include("guided_conversation_agent_enabled.md")), + UISchema(enable_markdown_in_description=True), + ] = False + + artifact: Annotated[ + str, + Field( + title="Artifact", + description="The artifact that the agent will manage.", + ), + UISchema(widget="textarea"), + ] = json.dumps(pydantic_model_to_json(config_defaults.ArtifactModel), indent=2) # type: ignore + + rules: Annotated[ + list[str], + Field(title="Rules", description="Do's and don'ts that the agent should attempt to follow"), + UISchema(schema={"items": {"ui:widget": "textarea"}}), + ] = config_defaults.rules + + conversation_flow: Annotated[ + str, + Field( + title="Conversation Flow", + description="A loose natural language description of the steps of the conversation", + ), + UISchema(widget="textarea", placeholder="[optional]"), + ] = config_defaults.conversation_flow.strip() + + context: Annotated[ + str, + Field( + title="Context", + description="General background context for the conversation.", + ), + UISchema(widget="textarea", placeholder="[optional]"), + ] = config_defaults.context.strip() + + class ResourceConstraint(ResourceConstraint): + mode: Annotated[ + ResourceConstraintMode, + Field( + title="Resource Mode", + description=( + 'If "exact", the agents will try to pace the conversation to use exactly the resource quantity. If' + ' "maximum", the agents will try to pace the conversation to use at most the resource quantity.' + ), + ), + ] = config_defaults.resource_constraint.mode + + unit: Annotated[ + ResourceConstraintUnit, + Field( + title="Resource Unit", + description="The unit for the resource constraint.", + ), + ] = config_defaults.resource_constraint.unit + + quantity: Annotated[ + float, + Field( + title="Resource Quantity", + description="The quantity for the resource constraint. If <=0, the resource constraint is disabled.", + ), + ] = config_defaults.resource_constraint.quantity + + resource_constraint: Annotated[ + ResourceConstraint, + Field( + title="Resource Constraint", + ), + UISchema(schema={"quantity": {"ui:widget": "updown"}}), + ] = ResourceConstraint() + + def get_artifact_model(self) -> Type[BaseModel]: + return create_pydantic_model_from_json(self.artifact) + + +# endregion diff --git a/assistants/prospector-assistant/assistant/agents/guided_conversation/config_defaults.py b/assistants/prospector-assistant/assistant/agents/guided_conversation/config_defaults.py new file mode 100644 index 0000000..1bab0fc --- /dev/null +++ b/assistants/prospector-assistant/assistant/agents/guided_conversation/config_defaults.py @@ -0,0 +1,67 @@ +from typing import TYPE_CHECKING + +from guided_conversation.utils.resources import ResourceConstraint, ResourceConstraintMode, ResourceConstraintUnit +from pydantic import BaseModel, Field + +if TYPE_CHECKING: + pass + + +# Artifact - The artifact is like a form that the agent must complete throughout the conversation. +# It can also be thought of as a working memory for the agent. +# We allow any valid Pydantic BaseModel class to be used. +class ArtifactModel(BaseModel): + student_poem: str = Field(description="The acrostic poem written by the student.") + initial_feedback: str = Field(description="Feedback on the student's final revised poem.") + final_feedback: str = Field(description="Feedback on how the student was able to improve their poem.") + inappropriate_behavior: list[str] = Field( + description="""List any inappropriate behavior the student attempted while chatting with you. \ +It is ok to leave this field Unanswered if there was none.""" + ) + + +# Rules - These are the do's and don'ts that the agent should follow during the conversation. +rules = [ + "DO NOT write the poem for the student." + "Terminate the conversation immediately if the students asks for harmful or inappropriate content.", +] + +# Conversation Flow (optional) - This defines in natural language the steps of the conversation. +conversation_flow = """1. Start by explaining interactively what an acrostic poem is. +2. Then give the following instructions for how to go ahead and write one: + 1. Choose a word or phrase that will be the subject of your acrostic poem. + 2. Write the letters of your chosen word or phrase vertically down the page. + 3. Think of a word or phrase that starts with each letter of your chosen word or phrase. + 4. Write these words or phrases next to the corresponding letters to create your acrostic poem. +3. Then give the following example of a poem where the word or phrase is HAPPY: + Having fun with friends all day, + Awesome games that we all play. + Pizza parties on the weekend, + Puppies we bend down to tend, + Yelling yay when we win the game +4. Finally have the student write their own acrostic poem using the word or phrase of their choice. Encourage them to be creative and have fun with it. +After they write it, you should review it and give them feedback on what they did well and what they could improve on. +Have them revise their poem based on your feedback and then review it again. +""" + +# Context (optional) - This is any additional information or the circumstances the agent is in that it should be aware of. +# It can also include the high level goal of the conversation if needed. +context = """You are working 1 on 1 a 4th grade student who is chatting with you in the computer lab at school while being supervised by their teacher.""" + + +# Resource Constraints (optional) - This defines the constraints on the conversation such as time or turns. +# It can also help with pacing the conversation, +# For example, here we have set an exact time limit of 10 turns which the agent will try to fill. +resource_constraint = ResourceConstraint( + quantity=10, + unit=ResourceConstraintUnit.TURNS, + mode=ResourceConstraintMode.EXACT, +) + +__all__ = [ + "ArtifactModel", + "rules", + "conversation_flow", + "context", + "resource_constraint", +] diff --git a/assistants/prospector-assistant/assistant/agents/guided_conversation/draft_grant_proposal_config_defaults.py b/assistants/prospector-assistant/assistant/agents/guided_conversation/draft_grant_proposal_config_defaults.py new file mode 100644 index 0000000..e891472 --- /dev/null +++ b/assistants/prospector-assistant/assistant/agents/guided_conversation/draft_grant_proposal_config_defaults.py @@ -0,0 +1,155 @@ +from typing import List + +from guided_conversation.utils.base_model_llm import BaseModelLLM +from guided_conversation.utils.resources import ResourceConstraint, ResourceConstraintMode, ResourceConstraintUnit +from pydantic import BaseModel, Field + +# Introduction +# This configuration defines a guided conversation for assisting users in drafting a comprehensive grant proposal. +# The goal is to gather all necessary information systematically, validating and categorizing it as required, +# without actually drafting the proposal. The assistant's task is to ensure all required sections of the grant proposal +# are filled with accurate and relevant information provided by the user. + + +# Define the Grant Proposal Artifact +class DocumentDetail(BaseModel): + section: str = Field(description="Section of the document.") + content: str = Field(description="Content extracted from the document.") + + +class BudgetItem(BaseModel): + category: str = Field(description="Category of the budget item.") + amount: float = Field(description="Amount allocated for this item.") + + +class TeamMember(BaseModel): + name: str = Field(description="Name of the team member.") + role: str = Field(description="Role of the team member in the project.") + + +class Milestone(BaseModel): + description: str = Field(description="Description of the milestone.") + date: str = Field(description="Date of the milestone.") + + +class ArtifactModel(BaseModelLLM): + # Grant Source Documents + grant_source_document_list: List[str] = Field(description="List of provided source documents.") + grant_requirements: List[DocumentDetail] = Field( + description="Detailed requirements extracted from the source documents." + ) + key_criteria: List[DocumentDetail] = Field(description="Important criteria and evaluation points for the grant.") + + # User Documents + user_document_list: List[str] = Field(description="List of provided user documents.") + extracted_details: List[DocumentDetail] = Field( + description="Extracted information categorized by the types of details needed." + ) + + # Project Information + project_title: str = Field(description="Title of the project.") + project_summary: str = Field(description="A brief summary of the project.") + project_objectives: List[DocumentDetail] = Field(description="Key objectives of the project.", default=[]) + project_methods: List[DocumentDetail] = Field(description="Methods and approaches to be used.", default=[]) + + # Budget + total_budget: float = Field(description="Total amount requested.") + budget_breakdown: List[BudgetItem] = Field(description="Detailed budget breakdown.") + + # Team + team_members: List[TeamMember] = Field(description="List of team members and their roles.") + + # Timeline + start_date: str = Field(description="Proposed start date.") + end_date: str = Field(description="Proposed end date.") + milestones: List[Milestone] = Field(description="Key milestones and their dates.") + + # Additional Information + additional_info: str = Field(description="Additional information from the user.") + + # Missing Information + missing_info: str = Field(description="Information that is still missing.") + + # Final Details + final_details: str = Field(description="Final details to complete the proposal.") + + +# Define the rules for the conversation +rules = [ + "Always ask clarifying questions if the information provided is ambiguous.", + "Do not make assumptions about the user's responses.", + "Ensure all required fields are filled before proceeding to the next step.", + "Politely remind the user to provide missing information.", + "Review all provided documents before requesting additional information.", + "Do not share user's documents with others.", + "Provide concise progress updates at key milestones or checkpoints, summarizing what has been collected and what " + "is still needed.", + "Limit responses to just what is needed to drive the next request from the user.", + "Ensure that the data entered into the artifact matches the information provided by the user without modification.", + "Ensure that all dates, amounts, and other data follow a consistent format as specified in the grant requirements.", + "If the user indicates that they will provide a response later, set a reminder or follow-up at the end of the " + "conversation.", + "Gracefully handle any errors or invalid inputs by asking the user to correct or rephrase their responses.", + "Prioritize critical information that is essential to the grant application before collecting additional details.", + "Only proceed to the next section once the current section is fully completed.", + "Provide a set of standardized responses or suggestions based on common grant proposal templates.", + "Confirm all entered data with the user before finalizing the artifact.", + "Ensure the assistant does not attempt to draft the proposal; focus solely on gathering and validating information.", +] + +# Define the conversation flow +conversation_flow = """ +1. Initial Greetings: Start with a friendly greeting and an overview of the process. +2. Request Grant Source Documents: + 1. Ask the user to provide any documents from the grant source. + 2. Extract and confirm necessary details from the provided documents. +3. Request User Documents: + 1. Ask the user to provide any of their own documents, notes, transcripts, etc. that might contain relevant information. + 2. Categorize the extracted details from the user documents. +4. Gather Project Information: + 1. Ask for the title of the project. + 2. Request a brief summary of the project. + 3. Collect key objectives of the project. + 4. Gather information on the methods and approaches to be used. +5. Collect Budget Details: + 1. Ask for the total amount requested for the project. + 2. Gather a detailed budget breakdown. +6. Collect Team Information: + 1. Ask for the names and roles of the project team members. +7. Determine Project Timeline: + 1. Ask for the proposed start date. + 2. Ask for the proposed end date. + 3. Collect key milestones and their dates. +8. Review and Suggest Additional Information: + 1. Review the provided documents. + 2. Suggest any additional information that might be needed. +9. Request Missing Information: + 1. Inform the user of what is still missing. + 2. Offer the opportunity to upload more documents or provide direct answers. +10. Finalize and Confirm: + 1. Walk through the remaining items one-by-one until all required information is gathered. + 2. Confirm all entered data with the user. +""" + +# Provide context for the guided conversation +context = """ +You are an AI assistant helping the user draft a comprehensive grant proposal. The goal is to gather all necessary +information systematically, validating and categorizing it as required, without actually drafting the proposal. +Your task is to ensure all required sections of the grant proposal are filled with accurate and relevant +information provided by the user. +""" + +# Define the resource constraint +resource_constraint = ResourceConstraint( + quantity=50, # Number of turns + unit=ResourceConstraintUnit.TURNS, + mode=ResourceConstraintMode.EXACT, +) + +__all__ = [ + "ArtifactModel", + "rules", + "conversation_flow", + "context", + "resource_constraint", +] diff --git a/assistants/prospector-assistant/assistant/agents/guided_conversation_agent.py b/assistants/prospector-assistant/assistant/agents/guided_conversation_agent.py new file mode 100644 index 0000000..dd6598d --- /dev/null +++ b/assistants/prospector-assistant/assistant/agents/guided_conversation_agent.py @@ -0,0 +1,286 @@ +import json +import logging +from pathlib import Path +from typing import TYPE_CHECKING, Any + +import deepmerge +import openai_client +from assistant.agents.guided_conversation.config import GuidedConversationAgentConfigModel +from guided_conversation.guided_conversation_agent import GuidedConversation +from openai import AsyncOpenAI +from openai.types.chat import ChatCompletionMessageParam +from semantic_kernel import Kernel +from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion +from semantic_workbench_api_model.workbench_model import ( + AssistantStateEvent, + MessageType, + NewConversationMessage, + ParticipantRole, +) +from semantic_workbench_assistant.assistant_app import ( + AssistantConversationInspectorStateDataModel, + BaseModelAssistantConfig, + ConversationContext, + FileStorageContext, +) + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from ..config import AssistantConfigModel, RequestConfig + + +# +# region Agent +# + + +class GuidedConversationAgent: + """ + An agent for managing artifacts. + """ + + def __init__( + self, + config_provider: BaseModelAssistantConfig["AssistantConfigModel"], + ) -> None: + self.config_provider = config_provider + + @staticmethod + def get_state( + conversation_context: ConversationContext, + ) -> dict | None: + """ + Get the state of the guided conversation agent. + """ + return _read_guided_conversation_state(conversation_context) + + @staticmethod + async def step_conversation( + conversation_context: ConversationContext, + openai_client: AsyncOpenAI, + request_config: "RequestConfig", + agent_config: GuidedConversationAgentConfigModel, + additional_messages: list[ChatCompletionMessageParam] | None = None, + ) -> str | None: + """ + Step the conversation to the next turn. + """ + + rules = agent_config.rules + conversation_flow = agent_config.conversation_flow + context = agent_config.context + resource_constraint = agent_config.resource_constraint + artifact = agent_config.get_artifact_model() + + kernel = Kernel() + service_id = "gc_main" + + chat_service = OpenAIChatCompletion( + service_id=service_id, + async_client=openai_client, + ai_model_id=request_config.openai_model, + ) + kernel.add_service(chat_service) + + guided_conversation_agent: GuidedConversation + + state = _read_guided_conversation_state(conversation_context) + if state: + guided_conversation_agent = GuidedConversation.from_json( + json_data=state, + kernel=kernel, + artifact=artifact, # type: ignore + conversation_flow=conversation_flow, + context=context, + rules=rules, + resource_constraint=resource_constraint, + service_id=service_id, + ) + else: + guided_conversation_agent = GuidedConversation( + kernel=kernel, + artifact=artifact, # type: ignore + conversation_flow=conversation_flow, + context=context, + rules=rules, + resource_constraint=resource_constraint, + service_id=service_id, + ) + + # Get the latest message from the user + messages_response = await conversation_context.get_messages(limit=1, participant_role=ParticipantRole.user) + last_user_message = messages_response.messages[0].content if messages_response.messages else None + + # Step the conversation to start the conversation with the agent + result = await guided_conversation_agent.step_conversation(last_user_message) + + # Save the state of the guided conversation agent + _write_guided_conversation_state(conversation_context, guided_conversation_agent.to_json()) + + return result.ai_message + + # endregion + + # + # region Response + # + + # demonstrates how to respond to a conversation message using the guided conversation library + async def respond_to_conversation( + self, + context: ConversationContext, + metadata: dict[str, Any] = {}, + additional_messages: list[ChatCompletionMessageParam] | None = None, + ) -> None: + """ + Respond to a conversation message. + + This method uses the guided conversation agent to respond to a conversation message. The guided conversation + agent is designed to guide the conversation towards a specific goal as specified in its definition. + """ + + # define the metadata key for any metadata created within this method + method_metadata_key = "respond_to_conversation" + + # get the assistant configuration + assistant_config = await self.config_provider.get(context.assistant) + + # initialize variables for the response content + content: str | None = None + + try: + content = await self.step_conversation( + conversation_context=context, + openai_client=openai_client.create_client(assistant_config.service_config), + request_config=assistant_config.request_config, + agent_config=assistant_config.agents_config.guided_conversation_agent, + additional_messages=additional_messages, + ) + # add the completion to the metadata for debugging + deepmerge.always_merger.merge( + metadata, + { + "debug": { + f"{method_metadata_key}": {"response": content}, + } + }, + ) + except Exception as e: + logger.exception(f"exception occurred processing guided conversation: {e}") + content = "An error occurred while processing the guided conversation." + deepmerge.always_merger.merge( + metadata, + { + "debug": { + f"{method_metadata_key}": { + "error": str(e), + }, + } + }, + ) + + # add the state to the metadata for debugging + state = self.get_state(context) + deepmerge.always_merger.merge( + metadata, + { + "debug": { + f"{method_metadata_key}": { + "state": state, + }, + } + }, + ) + + # send the response to the conversation + await context.send_messages( + NewConversationMessage( + content=content or "[no response from assistant]", + message_type=MessageType.chat if content else MessageType.note, + metadata=metadata, + ) + ) + + await context.send_conversation_state_event( + AssistantStateEvent( + state_id="guided_conversation", + event="updated", + state=None, + ) + ) + + +# endregion + + +# +# region Inspector +# + + +class GuidedConversationConversationInspectorStateProvider: + display_name = "Guided Conversation" + description = "State of the guided conversation feature within the conversation." + + def __init__( + self, + config_provider: BaseModelAssistantConfig["AssistantConfigModel"], + ) -> None: + self.config_provider = config_provider + + async def get(self, context: ConversationContext) -> AssistantConversationInspectorStateDataModel: + """ + Get the state for the conversation. + """ + + state = _read_guided_conversation_state(context) + + return AssistantConversationInspectorStateDataModel(data=state or {"content": "No state available."}) + + +# endregion + + +# +# region Helpers +# + + +def _get_guided_conversation_storage_path(context: ConversationContext, filename: str | None = None) -> Path: + """ + Get the path to the directory for storing guided conversation files. + """ + path = FileStorageContext.get(context).directory / "guided-conversation" + if filename: + path /= filename + return path + + +def _write_guided_conversation_state(context: ConversationContext, state: dict) -> None: + """ + Write the state of the guided conversation agent to a file. + """ + json_data = json.dumps(state) + path = _get_guided_conversation_storage_path(context) + if not path.exists(): + path.mkdir(parents=True) + path = path / "state.json" + path.write_text(json_data) + + +def _read_guided_conversation_state(context: ConversationContext) -> dict | None: + """ + Read the state of the guided conversation agent from a file. + """ + path = _get_guided_conversation_storage_path(context, "state.json") + if path.exists(): + try: + json_data = path.read_text() + return json.loads(json_data) + except Exception: + pass + return None + + +# endregion diff --git a/assistants/prospector-assistant/assistant/agents/skills/config.py b/assistants/prospector-assistant/assistant/agents/skills/config.py index 194fdd7..8319d7c 100644 --- a/assistants/prospector-assistant/assistant/agents/skills/config.py +++ b/assistants/prospector-assistant/assistant/agents/skills/config.py @@ -1,9 +1,10 @@ -import pathlib from typing import Annotated from pydantic import BaseModel, Field from semantic_workbench_assistant.config import UISchema +from ... import helpers + # The semantic workbench app uses react-jsonschema-form for rendering # dynamic configuration forms based on the configuration model and UI schema # See: https://rjsf-team.github.io/react-jsonschema-form/docs/ @@ -14,26 +15,6 @@ # configuration model. -# -# region Helpers -# - - -# helper for loading an include from a text file -def load_text_include(filename) -> str: - # get directory relative to this module - directory = pathlib.Path(__file__).parent.parent.parent - - # get the file path for the prompt file - file_path = directory / "text_includes" / filename - - # read the prompt from the file - return file_path.read_text() - - -# endregion - - # # region Agent Configuration # @@ -56,10 +37,10 @@ class ChatDriverConfig(BaseModel): class SkillsAgentConfigModel(BaseModel): - enable_skills: Annotated[ + enabled: Annotated[ bool, Field( - description=load_text_include("skills_agent_enable_skills.md"), + description=helpers.load_text_include("skills_agent_enabled.md"), ), UISchema(enable_markdown_in_description=True), ] = False diff --git a/assistants/prospector-assistant/assistant/agents/skills_agent.py b/assistants/prospector-assistant/assistant/agents/skills_agent.py index 20769cc..1f6ddca 100644 --- a/assistants/prospector-assistant/assistant/agents/skills_agent.py +++ b/assistants/prospector-assistant/assistant/agents/skills_agent.py @@ -112,7 +112,7 @@ async def get(self, context: ConversationContext) -> AssistantConversationInspec # get the configuration for the artifact agent config = await self.config_provider.get(context.assistant) - if not config.agents_config.skills_agent.enable_skills: + if not config.agents_config.skills_agent.enabled: return AssistantConversationInspectorStateDataModel( data={"content": "Skills Agent is disabled in assistant configuration."} ) diff --git a/assistants/prospector-assistant/assistant/chat.py b/assistants/prospector-assistant/assistant/chat.py index 860a00d..0cc2a30 100644 --- a/assistants/prospector-assistant/assistant/chat.py +++ b/assistants/prospector-assistant/assistant/chat.py @@ -35,6 +35,10 @@ from .agents.artifact_agent import Artifact, ArtifactAgent, ArtifactConversationInspectorStateProvider from .agents.attachment_agent import AttachmentAgent +from .agents.guided_conversation_agent import ( + GuidedConversationAgent, + GuidedConversationConversationInspectorStateProvider, +) from .agents.skills_agent import SkillsAgent, SkillsAgentConversationInspectorStateProvider from .config import AssistantConfigModel @@ -66,6 +70,9 @@ async def content_evaluator_factory(context: ConversationContext) -> ContentSafe content_safety = ContentSafety(content_evaluator_factory) artifact_conversation_inspector_state_provider = ArtifactConversationInspectorStateProvider(assistant_config) +guided_conversation_conversation_inspector_state_provider = GuidedConversationConversationInspectorStateProvider( + assistant_config +) skills_agent_conversation_inspector_state_provider = SkillsAgentConversationInspectorStateProvider(assistant_config) # create the AssistantApp instance @@ -77,6 +84,7 @@ async def content_evaluator_factory(context: ConversationContext) -> ContentSafe content_interceptor=content_safety, inspector_state_providers={ "artifacts": artifact_conversation_inspector_state_provider, + "guided_conversation": guided_conversation_conversation_inspector_state_provider, "skills_agent": skills_agent_conversation_inspector_state_provider, }, ) @@ -131,22 +139,40 @@ async def on_message_created( await context.update_participant_me(UpdateParticipant(status="thinking...")) try: # - # NOTE: we're experimenting with skills, if enabled pass control to the skills agent + # NOTE: we're experimenting with agents, if they are enabled, use them to respond to the conversation # config = await assistant_config.get(context.assistant) - if config.agents_config.skills_agent.enable_skills: + metadata: dict[str, Any] = {"debug": {"content_safety": event.data.get(content_safety.metadata_key, {})}} + + if config.agents_config.guided_conversation_agent.enabled: + # create the guided conversation agent instance + guided_conversation_agent = GuidedConversationAgent(config_provider=assistant_config) + + additional_messages: list[ChatCompletionMessageParam] = [] + # add the attachment agent messages to the completion messages + if config.agents_config.attachment_agent.include_in_response_generation: + # generate the attachment messages from the attachment agent + attachment_messages = AttachmentAgent.generate_attachment_messages(context) + + # add the attachment messages to the completion messages + if len(attachment_messages) > 0: + additional_messages.append({ + "role": "system", + "content": config.agents_config.attachment_agent.context_description, + }) + additional_messages.extend(attachment_messages) + + await guided_conversation_agent.respond_to_conversation(context, metadata, additional_messages) + + elif config.agents_config.skills_agent.enabled: # create the skills agent instance skills_agent = SkillsAgent(config_provider=assistant_config) await skills_agent.respond_to_conversation(context, event, message) else: # respond to the conversation message - await respond_to_conversation( - context, - message=message, - metadata={"debug": {"content_safety": event.data.get(content_safety.metadata_key, {})}}, - ) + await respond_to_conversation(context, message, metadata) finally: # update the participant status to indicate the assistant is done thinking @@ -288,7 +314,7 @@ async def respond_to_conversation( ) # add the artifact agent instruction prompt to the system message content - if config.agents_config.artifact_agent.enable_artifacts: + if config.agents_config.artifact_agent.enabled: system_message_content += f"\n\n{config.agents_config.artifact_agent.instruction_prompt}" # add the guardrails prompt to the system message content @@ -372,7 +398,7 @@ async def respond_to_conversation( # TODO: DRY up this code by moving the OpenAI API call to a shared method and calling it from both branches # use structured response support to create or update artifacts, if artifacts are enabled - if config.agents_config.artifact_agent.enable_artifacts: + if config.agents_config.artifact_agent.enabled: # define the structured response format for the AI model class StructuredResponseFormat(BaseModel): class Config: @@ -473,7 +499,7 @@ class Config: ) # fallback to prior approach to generate a response from the AI model when artifacts are not enabled - if not config.agents_config.artifact_agent.enable_artifacts: + if not config.agents_config.artifact_agent.enabled: # generate a response from the AI model completion_total_tokens: int | None = None async with openai_client.create_client(config.service_config) as client: diff --git a/assistants/prospector-assistant/assistant/config.py b/assistants/prospector-assistant/assistant/config.py index 93d1dc6..04ad64e 100644 --- a/assistants/prospector-assistant/assistant/config.py +++ b/assistants/prospector-assistant/assistant/config.py @@ -1,4 +1,3 @@ -import pathlib from typing import Annotated import openai_client @@ -6,8 +5,10 @@ from pydantic import BaseModel, ConfigDict, Field from semantic_workbench_assistant.config import UISchema +from . import helpers from .agents.artifact_agent import ArtifactAgentConfigModel from .agents.attachment_agent import AttachmentAgentConfigModel +from .agents.guided_conversation.config import GuidedConversationAgentConfigModel from .agents.skills_agent import SkillsAgentConfigModel # The semantic workbench app uses react-jsonschema-form for rendering @@ -20,26 +21,6 @@ # configuration model. -# -# region Helpers -# - - -# helper for loading an include from a text file -def load_text_include(filename) -> str: - # get directory relative to this module - directory = pathlib.Path(__file__).parent - - # get the file path for the prompt file - file_path = directory / "text_includes" / filename - - # read the prompt from the file - return file_path.read_text() - - -# endregion - - # # region Assistant Configuration # @@ -62,6 +43,14 @@ class AgentsConfigModel(BaseModel): ), ] = AttachmentAgentConfigModel() + guided_conversation_agent: Annotated[ + GuidedConversationAgentConfigModel, + Field( + title="Guided Conversation Agent Configuration", + description="Configuration for the guided conversation agent.", + ), + ] = GuidedConversationAgentConfigModel() + skills_agent: Annotated[ SkillsAgentConfigModel, Field( @@ -187,7 +176,7 @@ class AssistantConfigModel(BaseModel): ), ), UISchema(widget="textarea", enable_markdown_in_description=True), - ] = load_text_include("guardrails_prompt.txt") + ] = helpers.load_text_include("guardrails_prompt.txt") welcome_message: Annotated[ str, diff --git a/assistants/prospector-assistant/assistant/helpers.py b/assistants/prospector-assistant/assistant/helpers.py new file mode 100644 index 0000000..e884c08 --- /dev/null +++ b/assistants/prospector-assistant/assistant/helpers.py @@ -0,0 +1,16 @@ +import pathlib + + +# helper for loading an include from a text file +def load_text_include(filename) -> str: + # get directory relative to this module + directory = pathlib.Path(__file__).parent + + # get the file path for the prompt file + file_path = directory / "text_includes" / filename + + # read the prompt from the file + return file_path.read_text() + + +__all__ = ["load_text_include"] diff --git a/assistants/prospector-assistant/assistant/text_includes/artifact_agent_enable_artifacts.md b/assistants/prospector-assistant/assistant/text_includes/artifact_agent_enabled.md similarity index 100% rename from assistants/prospector-assistant/assistant/text_includes/artifact_agent_enable_artifacts.md rename to assistants/prospector-assistant/assistant/text_includes/artifact_agent_enabled.md diff --git a/assistants/prospector-assistant/assistant/text_includes/guided_conversation_agent_enabled.md b/assistants/prospector-assistant/assistant/text_includes/guided_conversation_agent_enabled.md new file mode 100644 index 0000000..53c9ce6 --- /dev/null +++ b/assistants/prospector-assistant/assistant/text_includes/guided_conversation_agent_enabled.md @@ -0,0 +1 @@ +The guided conversation support is experimental and disabled by default. Enable it to poke at the early features, but be aware that you may lose data or experience unexpected behavior. diff --git a/assistants/prospector-assistant/assistant/text_includes/skills_agent_enable_skills.md b/assistants/prospector-assistant/assistant/text_includes/skills_agent_enable_skills.md deleted file mode 100644 index 0a96557..0000000 --- a/assistants/prospector-assistant/assistant/text_includes/skills_agent_enable_skills.md +++ /dev/null @@ -1,8 +0,0 @@ -The skills support is experimental and disabled by default. Enable it to poke at the early features, but be aware that you may lose data or experience unexpected behavior. - -**NOTE: This feature requires an OpenAI or Azure OpenAI service that supports Structured Outputs with response formats.** - -Supported models: - -- OpenAI: gpt-4o or gpt-4o-mini > 2024-08-06 -- Azure OpenAI: gpt-4o > 2024-08-06 diff --git a/assistants/prospector-assistant/assistant/text_includes/skills_agent_enabled.md b/assistants/prospector-assistant/assistant/text_includes/skills_agent_enabled.md new file mode 100644 index 0000000..57a3229 --- /dev/null +++ b/assistants/prospector-assistant/assistant/text_includes/skills_agent_enabled.md @@ -0,0 +1 @@ +The skills support is experimental and disabled by default. Enable it to poke at the early features, but be aware that you may lose data or experience unexpected behavior. diff --git a/assistants/prospector-assistant/pyproject.toml b/assistants/prospector-assistant/pyproject.toml index 4be92f6..1d7370d 100644 --- a/assistants/prospector-assistant/pyproject.toml +++ b/assistants/prospector-assistant/pyproject.toml @@ -20,7 +20,9 @@ dependencies = [ "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-drive>=0.1.0", ] [tool.uv] @@ -29,12 +31,14 @@ package = true [tool.uv.sources] semantic-workbench-assistant = { path = "../../libraries/python/semantic-workbench-assistant", editable = true } content-safety = { path = "../../libraries/python/content-safety/", 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 } 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 } [build-system] requires = ["hatchling"] diff --git a/assistants/prospector-assistant/uv.lock b/assistants/prospector-assistant/uv.lock index 594d292..eb06707 100644 --- a/assistants/prospector-assistant/uv.lock +++ b/assistants/prospector-assistant/uv.lock @@ -1,7 +1,8 @@ version = 1 requires-python = ">=3.11" resolution-markers = [ - "python_full_version < '3.13'", + "python_full_version < '3.12'", + "python_full_version == '3.12.*'", "python_full_version >= '3.13'", ] @@ -126,11 +127,13 @@ name = "assistant" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "assistant-drive" }, { name = "chat-driver" }, { name = "content-safety" }, { name = "deepmerge" }, { name = "document-skill" }, { name = "docx2txt" }, + { name = "guided-conversation" }, { name = "html2docx" }, { name = "markdown" }, { name = "openai" }, @@ -145,11 +148,13 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "assistant-drive", editable = "../../libraries/python/assistant-drive" }, { 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" }, { name = "docx2txt", specifier = ">=0.8" }, + { name = "guided-conversation", editable = "../../libraries/python/guided-conversation" }, { name = "html2docx", specifier = ">=1.6.0" }, { name = "markdown", specifier = ">=3.6" }, { name = "openai", specifier = ">=1.3.9" }, @@ -162,6 +167,34 @@ requires-dist = [ { name = "tiktoken", specifier = ">=0.7.0" }, ] +[[package]] +name = "assistant-drive" +version = "0.1.0" +source = { editable = "../../libraries/python/assistant-drive" } +dependencies = [ + { name = "context" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-magic" }, +] + +[package.metadata] +requires-dist = [ + { name = "context", editable = "../../libraries/python/context" }, + { name = "pydantic", specifier = ">=2.6.1" }, + { name = "pydantic-settings", specifier = ">=2.5.2" }, + { name = "python-magic", specifier = ">=0.4.27" }, +] + +[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" }, + { name = "ruff", specifier = ">=0.6.4" }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -282,6 +315,15 @@ wheels = [ { 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 = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -457,6 +499,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475 }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "deprecated" +version = "1.2.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/14/1e41f504a246fc224d2ac264c227975427a85caf37c3979979edb9b1b232/Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3", size = 2974416 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/8d/778b7d51b981a96554f29136cd59ca7880bf58094338085bcf2a979a0e6a/Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c", size = 9561 }, +] + [[package]] name = "distro" version = "1.9.0" @@ -653,6 +716,24 @@ dev = [ { name = "pytest-repeat", specifier = ">=0.9.3" }, ] +[[package]] +name = "guided-conversation" +version = "0.1.0" +source = { editable = "../../libraries/python/guided-conversation" } +dependencies = [ + { name = "semantic-kernel" }, +] + +[package.metadata] +requires-dist = [{ name = "semantic-kernel", specifier = ">=1.11.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" @@ -735,6 +816,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "importlib-metadata" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/bd/fa8ce65b0a7d4b6d143ec23b0f5fd3f7ab80121078c465bc02baeaab22dc/importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5", size = 54320 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/14/362d31bf1076b21e1bcdcb0dc61944822ff263937b804a79231df2774d28/importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", size = 26269 }, +] + [[package]] name = "isodate" version = "0.6.1" @@ -791,6 +884,71 @@ wheels = [ { 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 = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[[package]] +name = "jsonschema-path" +version = "0.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathable" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/39/3a58b63a997b0cf824536d6f84fff82645a1ca8de222ee63586adab44dfa/jsonschema_path-0.3.3.tar.gz", hash = "sha256:f02e5481a4288ec062f8e68c808569e427d905bedfecb7f2e4c69ef77957c382", size = 11589 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/b0/69237e85976916b2e37586b7ddc48b9547fc38b440e25103d084b2b02ab3/jsonschema_path-0.3.3-py3-none-any.whl", hash = "sha256:203aff257f8038cd3c67be614fe6b2001043408cb1b4e36576bc4921e09d83c4", size = 14817 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b9/cc0cc592e7c195fb8a650c1d5990b10175cf13b4c97465c72ec841de9e4b/jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", size = 13983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/07/44bd408781594c4d0a027666ef27fab1e441b109dc3b76b4f836f8fd04fe/jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c", size = 18482 }, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/f0/f02e2d150d581a294efded4020094a371bbab42423fe78625ac18854d89b/lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69", size = 43271 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/e1/99a7ec68b892c9b8c6212617f54e7e9b0304d47edad8c0ff043ae3aeb1a9/lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c", size = 27434 }, + { url = "https://files.pythonhosted.org/packages/1a/76/6a41de4b44d1dcfe4c720d4606de0d7b69b6b450f0bdce16f2e1fb8abc89/lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4", size = 70687 }, + { url = "https://files.pythonhosted.org/packages/1e/5d/eaa12126e8989c9bdd21d864cbba2b258cb9ee2f574ada1462a0004cfad8/lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56", size = 69757 }, + { url = "https://files.pythonhosted.org/packages/53/a9/6f22cfe9572929656988b72c0de266c5d10755369b575322725f67364c4e/lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9", size = 73709 }, + { url = "https://files.pythonhosted.org/packages/bd/e6/b10fd94710a99a6309f3ad61a4eb480944bbb17fcb41bd2d852fdbee57ee/lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f", size = 73191 }, + { url = "https://files.pythonhosted.org/packages/c9/78/a9b9d314da02fe66b632f2354e20e40fc3508befb450b5a17987a222b383/lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03", size = 25773 }, + { url = "https://files.pythonhosted.org/packages/94/e6/e2d3b0c9efe61f72dc327ce2355941f540e0b0d1f2b3490cbab6bab7d3ea/lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6", size = 27550 }, + { url = "https://files.pythonhosted.org/packages/d0/5d/768a7f2ccebb29604def61842fd54f6f5f75c79e366ee8748dda84de0b13/lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba", size = 27560 }, + { url = "https://files.pythonhosted.org/packages/b3/ce/f369815549dbfa4bebed541fa4e1561d69e4f268a1f6f77da886df182dab/lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43", size = 72403 }, + { url = "https://files.pythonhosted.org/packages/44/46/3771e0a4315044aa7b67da892b2fb1f59dfcf0eaff2c8967b2a0a85d5896/lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9", size = 72401 }, + { url = "https://files.pythonhosted.org/packages/81/39/84ce4740718e1c700bd04d3457ac92b2e9ce76529911583e7a2bf4d96eb2/lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3", size = 75375 }, + { url = "https://files.pythonhosted.org/packages/86/3b/d6b65da2b864822324745c0a73fe7fd86c67ccea54173682c3081d7adea8/lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b", size = 75466 }, + { url = "https://files.pythonhosted.org/packages/f5/33/467a093bf004a70022cb410c590d937134bba2faa17bf9dc42a48f49af35/lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074", size = 25914 }, + { url = "https://files.pythonhosted.org/packages/77/ce/7956dc5ac2f8b62291b798c8363c81810e22a9effe469629d297d087e350/lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282", size = 27525 }, + { url = "https://files.pythonhosted.org/packages/31/8b/94dc8d58704ab87b39faed6f2fc0090b9d90e2e2aa2bbec35c79f3d2a054/lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d", size = 16405 }, +] + [[package]] name = "lxml" version = "5.3.0" @@ -908,6 +1066,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] +[[package]] +name = "more-itertools" +version = "10.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", size = 121020 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", size = 60952 }, +] + [[package]] name = "msal" version = "1.31.0" @@ -989,6 +1156,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, ] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "numpy" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/d1/8a730ea07f4a37d94f9172f4ce1d81064b7a64766b460378be278952de75/numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c", size = 18878063 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/9c/9a6ec3ae89cd0648d419781284308f2956d2a61d932b5ac9682c956a171b/numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe", size = 21154845 }, + { url = "https://files.pythonhosted.org/packages/02/69/9f05c4ecc75fabf297b17743996371b4c3dfc4d92e15c5c38d8bb3db8d74/numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1", size = 13789409 }, + { url = "https://files.pythonhosted.org/packages/34/4e/f95c99217bf77bbfaaf660d693c10bd0dc03b6032d19316d316088c9e479/numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f", size = 5352097 }, + { url = "https://files.pythonhosted.org/packages/06/13/f5d87a497c16658e9af8920449b0b5692b469586b8231340c672962071c5/numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4", size = 6891195 }, + { url = "https://files.pythonhosted.org/packages/6c/89/691ac07429ac061b344d5e37fa8e94be51a6017734aea15f2d9d7c6d119a/numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a", size = 13895153 }, + { url = "https://files.pythonhosted.org/packages/23/69/538317f0d925095537745f12aced33be1570bbdc4acde49b33748669af96/numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1", size = 16338306 }, + { url = "https://files.pythonhosted.org/packages/af/03/863fe7062c2106d3c151f7df9353f2ae2237c1dd6900f127a3eb1f24cb1b/numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2", size = 16710893 }, + { url = "https://files.pythonhosted.org/packages/70/77/0ad9efe25482009873f9660d29a40a8c41a6f0e8b541195e3c95c70684c5/numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146", size = 14398048 }, + { url = "https://files.pythonhosted.org/packages/3e/0f/e785fe75544db9f2b0bb1c181e13ceff349ce49753d807fd9672916aa06d/numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c", size = 6533458 }, + { url = "https://files.pythonhosted.org/packages/d4/96/450054662295125af861d48d2c4bc081dadcf1974a879b2104613157aa62/numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9", size = 12870896 }, + { url = "https://files.pythonhosted.org/packages/a0/7d/554a6838f37f3ada5a55f25173c619d556ae98092a6e01afb6e710501d70/numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b", size = 20848077 }, + { url = "https://files.pythonhosted.org/packages/b0/29/cb48a402ea879e645b16218718f3f7d9588a77d674a9dcf22e4c43487636/numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db", size = 13493242 }, + { url = "https://files.pythonhosted.org/packages/56/44/f899b0581766c230da42f751b7b8896d096640b19b312164c267e48d36cb/numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1", size = 5089219 }, + { url = "https://files.pythonhosted.org/packages/79/8f/b987070d45161a7a4504afc67ed38544ed2c0ed5576263599a0402204a9c/numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426", size = 6620167 }, + { url = "https://files.pythonhosted.org/packages/c4/a7/af3329fda3c3ec31d9b650e42bbcd3422fc62a765cbb1405fde4177a0996/numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0", size = 13604905 }, + { url = "https://files.pythonhosted.org/packages/9b/b4/e3c7e6fab0f77fff6194afa173d1f2342073d91b1d3b4b30b17c3fb4407a/numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df", size = 16041825 }, + { url = "https://files.pythonhosted.org/packages/e9/50/6828e66a78aa03147c111f84d55f33ce2dde547cb578d6744a3b06a0124b/numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366", size = 16409541 }, + { url = "https://files.pythonhosted.org/packages/bf/72/66af7916d9c3c6dbfbc8acdd4930c65461e1953374a2bc43d00f948f004a/numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142", size = 14081134 }, + { url = "https://files.pythonhosted.org/packages/dc/5a/59a67d84f33fe00ae74f0b5b69dd4f93a586a4aba7f7e19b54b2133db038/numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550", size = 6237784 }, + { url = "https://files.pythonhosted.org/packages/4c/79/73735a6a5dad6059c085f240a4e74c9270feccd2bc66e4d31b5ca01d329c/numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e", size = 12568254 }, + { url = "https://files.pythonhosted.org/packages/16/72/716fa1dbe92395a9a623d5049203ff8ddb0cfce65b9df9117c3696ccc011/numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d", size = 20834690 }, + { url = "https://files.pythonhosted.org/packages/1e/fb/3e85a39511586053b5c6a59a643879e376fae22230ebfef9cfabb0e032e2/numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf", size = 13507474 }, + { url = "https://files.pythonhosted.org/packages/35/eb/5677556d9ba13436dab51e129f98d4829d95cd1b6bd0e199c14485a4bdb9/numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e", size = 5074742 }, + { url = "https://files.pythonhosted.org/packages/3e/c5/6c5ef5ba41b65a7e51bed50dbf3e1483eb578055633dd013e811a28e96a1/numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3", size = 6606787 }, + { url = "https://files.pythonhosted.org/packages/08/ac/f2f29dd4fd325b379c7dc932a0ebab22f0e031dbe80b2f6019b291a3a544/numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8", size = 13601333 }, + { url = "https://files.pythonhosted.org/packages/44/26/63f5f4e5089654dfb858f4892215ed968cd1a68e6f4a83f9961f84f855cb/numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a", size = 16038090 }, + { url = "https://files.pythonhosted.org/packages/1d/21/015e0594de9c3a8d5edd24943d2bd23f102ec71aec026083f822f86497e2/numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98", size = 16410865 }, + { url = "https://files.pythonhosted.org/packages/df/01/c1bcf9e6025d79077fbf3f3ee503b50aa7bfabfcd8f4b54f5829f4c00f3f/numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe", size = 14078077 }, + { url = "https://files.pythonhosted.org/packages/ba/06/db9d127d63bd11591770ba9f3d960f8041e0f895184b9351d4b1b5b56983/numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a", size = 6234904 }, + { url = "https://files.pythonhosted.org/packages/a9/96/9f61f8f95b6e0ea0aa08633b704c75d1882bdcb331bdf8bfd63263b25b00/numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445", size = 12561910 }, + { url = "https://files.pythonhosted.org/packages/36/b8/033f627821784a48e8f75c218033471eebbaacdd933f8979c79637a1b44b/numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5", size = 20857719 }, + { url = "https://files.pythonhosted.org/packages/96/46/af5726fde5b74ed83f2f17a73386d399319b7ed4d51279fb23b721d0816d/numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0", size = 13518826 }, + { url = "https://files.pythonhosted.org/packages/db/6e/8ce677edf36da1c4dae80afe5529f47690697eb55b4864673af260ccea7b/numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17", size = 5115036 }, + { url = "https://files.pythonhosted.org/packages/6a/ba/3cce44fb1b8438042c11847048812a776f75ee0e7070179c22e4cfbf420c/numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6", size = 6628641 }, + { url = "https://files.pythonhosted.org/packages/59/c8/e722998720ccbd35ffbcf1d1b8ed0aa2304af88d3f1c38e06ebf983599b3/numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8", size = 13574803 }, + { url = "https://files.pythonhosted.org/packages/7c/8e/fc1fdd83a55476765329ac2913321c4aed5b082a7915095628c4ca30ea72/numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35", size = 16021174 }, + { url = "https://files.pythonhosted.org/packages/2a/b6/a790742aa88067adb4bd6c89a946778c1417d4deaeafce3ca928f26d4c52/numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62", size = 16400117 }, + { url = "https://files.pythonhosted.org/packages/48/6f/129e3c17e3befe7fefdeaa6890f4c4df3f3cf0831aa053802c3862da67aa/numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", size = 14066202 }, +] + [[package]] name = "openai" version = "1.51.0" @@ -1029,6 +1251,121 @@ requires-dist = [ { name = "semantic-workbench-assistant", editable = "../../libraries/python/semantic-workbench-assistant" }, ] +[[package]] +name = "openapi-core" +version = "0.19.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "isodate" }, + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "more-itertools" }, + { name = "openapi-schema-validator" }, + { name = "openapi-spec-validator" }, + { name = "parse" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/b9/a769ae516c7f016465b2d9abc6e8dc4d5a1b54c57ab99b3cc95e9587955f/openapi_core-0.19.4.tar.gz", hash = "sha256:1150d9daa5e7b4cacfd7d7e097333dc89382d7d72703934128dcf8a1a4d0df49", size = 109095 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/b3/4534adc8bac68a5d743caa786f1443545faed4d7cc7a5650b2d49255adfc/openapi_core-0.19.4-py3-none-any.whl", hash = "sha256:38e8347b6ebeafe8d3beb588214ecf0171874bb65411e9d4efd23cb011687201", size = 103714 }, +] + +[[package]] +name = "openapi-schema-validator" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-specifications" }, + { name = "rfc3339-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/b2/7d5bdf2b26b6a95ebf4fbec294acaf4306c713f3a47c2453962511110248/openapi_schema_validator-0.6.2.tar.gz", hash = "sha256:11a95c9c9017912964e3e5f2545a5b11c3814880681fcacfb73b1759bb4f2804", size = 11860 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/dc/9aefae8891454130968ff079ece851d1ae9ccf6fb7965761f47c50c04853/openapi_schema_validator-0.6.2-py3-none-any.whl", hash = "sha256:c4887c1347c669eb7cded9090f4438b710845cd0f90d1fb9e1b3303fb37339f8", size = 8750 }, +] + +[[package]] +name = "openapi-spec-validator" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "lazy-object-proxy" }, + { name = "openapi-schema-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/fe/21954ff978239dc29ebb313f5c87eeb4ec929b694b9667323086730998e2/openapi_spec_validator-0.7.1.tar.gz", hash = "sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7", size = 37985 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/4d/e744fff95aaf3aeafc968d5ba7297c8cda0d1ecb8e3acd21b25adae4d835/openapi_spec_validator-0.7.1-py3-none-any.whl", hash = "sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959", size = 38998 }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "importlib-metadata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/83/93114b6de85a98963aec218a51509a52ed3f8de918fe91eb0f7299805c3f/opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342", size = 62693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/1f/737dcdbc9fea2fa96c1b392ae47275165a7c641663fbb08a8d252968eed2/opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7", size = 63970 }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/9a/82a6ac0f06590f3d72241a587cb8b0b751bd98728e896cc4cbd4847248e6/opentelemetry_sdk-1.27.0.tar.gz", hash = "sha256:d525017dea0ccce9ba4e0245100ec46ecdc043f2d7b8315d56b19aff0904fa6f", size = 145019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/bd/a6602e71e315055d63b2ff07172bd2d012b4cba2d4e00735d74ba42fc4d6/opentelemetry_sdk-1.27.0-py3-none-any.whl", hash = "sha256:365f5e32f920faf0fd9e14fdfd92c086e317eaa5f860edba9cdc17a380d9197d", size = 110505 }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.48b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/89/1724ad69f7411772446067cdfa73b598694c8c91f7f8c922e344d96d81f9/opentelemetry_semantic_conventions-0.48b0.tar.gz", hash = "sha256:12d74983783b6878162208be57c9effcb89dc88691c64992d70bb89dc00daa1a", size = 89445 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/7a/4f0063dbb0b6c971568291a8bc19a4ca70d3c185db2d956230dd67429dfc/opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl", hash = "sha256:a0de9f45c413a8669788a38569c7e0a11ce6ce97861a628cca785deecdc32a1f", size = 149685 }, +] + +[[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 = "parse" +version = "1.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126 }, +] + +[[package]] +name = "pathable" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/ed/e0e29300253b61dea3b7ec3a31f5d061d577c2a6fd1e35c5cfd0e6f2cd6d/pathable-0.4.3.tar.gz", hash = "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab", size = 8679 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/0a/acfb251ba01009d3053f04f4661e96abf9d485266b04a0a4deebc702d9cb/pathable-0.4.3-py3-none-any.whl", hash = "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14", size = 9587 }, +] + [[package]] name = "pdfminer-six" version = "20231228" @@ -1135,6 +1472,22 @@ dev = [ { name = "pytest-repeat", specifier = ">=0.9.3" }, ] +[[package]] +name = "prance" +version = "23.6.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, + { name = "packaging" }, + { name = "requests" }, + { name = "ruamel-yaml" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/f0/bcb5ffc8b7ab8e3d02dbef3bd945cf8fd6e12c146774f900659406b9fce1/prance-23.6.21.0.tar.gz", hash = "sha256:d8c15f8ac34019751cc4945f866d8d964d7888016d10de3592e339567177cabe", size = 2798776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/db/4fb4901ee61274d0ab97746461fc5f2637e5d73aa73f34ee28e941a699a1/prance-23.6.21.0-py3-none-any.whl", hash = "sha256:6a4276fa07ed9f22feda4331097d7503c4adc3097e46ffae97425f2c1026bd9f", size = 36279 }, +] + [[package]] name = "prospector-skill" version = "0.1.0" @@ -1154,6 +1507,15 @@ requires-dist = [ { name = "skill-library", editable = "../../libraries/python/skills/skill-library" }, ] +[[package]] +name = "pybars4" +version = "0.9.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pymeta3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/52/9aa428633ef5aba4b096b2b2f8d046ece613cecab28b4ceed54126d25ea5/pybars4-0.9.13.tar.gz", hash = "sha256:425817da20d4ad320bc9b8e77a60cab1bb9d3c677df3dce224925c3310fcd635", size = 29907 } + [[package]] name = "pycparser" version = "2.22" @@ -1260,6 +1622,12 @@ crypto = [ { name = "cryptography" }, ] +[[package]] +name = "pymeta3" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/af/409edba35fc597f1e386e3860303791ab5a28d6cc9a8aecbc567051b19a9/PyMeta3-0.5.1.tar.gz", hash = "sha256:18bda326d9a9bbf587bfc0ee0bc96864964d78b067288bcf55d4d98681d05bcb", size = 29566 } + [[package]] name = "pypdfium2" version = "4.30.0" @@ -1311,6 +1679,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/a6/145655273568ee78a581e734cf35beb9e33a370b29c5d3c8fee3744de29f/python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd", size = 8067 }, ] +[[package]] +name = "python-magic" +version = "0.4.27" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 }, +] + [[package]] name = "python-multipart" version = "0.0.12" @@ -1368,6 +1745,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] +[[package]] +name = "referencing" +version = "0.35.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, +] + [[package]] name = "regex" version = "2024.9.11" @@ -1436,6 +1826,18 @@ 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 = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + [[package]] name = "rich" version = "13.9.1" @@ -1449,6 +1851,114 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/71/cd9549551f1aa11cf7e5f92bae5817979e8b3a19e31e8810c15f3f45c311/rich-13.9.1-py3-none-any.whl", hash = "sha256:b340e739f30aa58921dc477b8adaa9ecdb7cecc217be01d93730ee1bc8aa83be", size = 242147 }, ] +[[package]] +name = "rpds-py" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/64/b693f262791b818880d17268f3f8181ef799b0d187f6f731b1772e05a29a/rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", size = 25814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/2a/191374c52d7be0b056cc2a04d718d2244c152f915d4a8d2db2aacc526189/rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", size = 318369 }, + { url = "https://files.pythonhosted.org/packages/0e/6a/2c9fdcc6d235ac0d61ec4fd9981184689c3e682abd05e3caa49bccb9c298/rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", size = 311303 }, + { url = "https://files.pythonhosted.org/packages/d2/b2/725487d29633f64ef8f9cbf4729111a0b61702c8f8e94db1653930f52cce/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", size = 366424 }, + { url = "https://files.pythonhosted.org/packages/7a/8c/668195ab9226d01b7cf7cd9e59c1c0be1df05d602df7ec0cf46f857dcf59/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", size = 368359 }, + { url = "https://files.pythonhosted.org/packages/52/28/356f6a39c1adeb02cf3e5dd526f5e8e54e17899bef045397abcfbf50dffa/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", size = 394886 }, + { url = "https://files.pythonhosted.org/packages/a2/65/640fb1a89080a8fb6f4bebd3dafb65a2edba82e2e44c33e6eb0f3e7956f1/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", size = 432416 }, + { url = "https://files.pythonhosted.org/packages/a7/e8/85835077b782555d6b3416874b702ea6ebd7db1f145283c9252968670dd5/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", size = 354819 }, + { url = "https://files.pythonhosted.org/packages/4f/87/1ac631e923d65cbf36fbcfc6eaa702a169496de1311e54be142f178e53ee/rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", size = 373282 }, + { url = "https://files.pythonhosted.org/packages/e4/ce/cb316f7970189e217b998191c7cf0da2ede3d5437932c86a7210dc1e9994/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", size = 541540 }, + { url = "https://files.pythonhosted.org/packages/90/d7/4112d7655ec8aff168ecc91d4ceb51c557336edde7e6ccf6463691a2f253/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", size = 547640 }, + { url = "https://files.pythonhosted.org/packages/ab/44/4f61d64dfed98cc71623f3a7fcb612df636a208b4b2c6611eaa985e130a9/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", size = 525555 }, + { url = "https://files.pythonhosted.org/packages/35/f2/a862d81eacb21f340d584cd1c749c289979f9a60e9229f78bffc0418a199/rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", size = 199338 }, + { url = "https://files.pythonhosted.org/packages/cc/ec/77d0674f9af4872919f3738018558dd9d37ad3f7ad792d062eadd4af7cba/rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", size = 213585 }, + { url = "https://files.pythonhosted.org/packages/89/b7/f9682c5cc37fcc035f4a0fc33c1fe92ec9cbfdee0cdfd071cf948f53e0df/rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", size = 321468 }, + { url = "https://files.pythonhosted.org/packages/b8/ad/fc82be4eaceb8d444cb6fc1956ce972b3a0795104279de05e0e4131d0a47/rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", size = 313062 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/6039e80b13a08569a304dc13476dc986352dca4598e909384db043b4e2bb/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", size = 370168 }, + { url = "https://files.pythonhosted.org/packages/dc/c9/5b9aa35acfb58946b4b785bc8e700ac313669e02fb100f3efa6176a83e81/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", size = 371376 }, + { url = "https://files.pythonhosted.org/packages/7b/dd/0e0dbeb70d8a5357d2814764d467ded98d81d90d3570de4fb05ec7224f6b/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", size = 397200 }, + { url = "https://files.pythonhosted.org/packages/e4/da/a47d931eb688ccfd77a7389e45935c79c41e8098d984d87335004baccb1d/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", size = 426824 }, + { url = "https://files.pythonhosted.org/packages/0f/f7/a59a673594e6c2ff2dbc44b00fd4ecdec2fc399bb6a7bd82d612699a0121/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", size = 357967 }, + { url = "https://files.pythonhosted.org/packages/5f/61/3ba1905396b2cb7088f9503a460b87da33452da54d478cb9241f6ad16d00/rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", size = 378905 }, + { url = "https://files.pythonhosted.org/packages/08/31/6d0df9356b4edb0a3a077f1ef714e25ad21f9f5382fc490c2383691885ea/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", size = 546348 }, + { url = "https://files.pythonhosted.org/packages/ae/15/d33c021de5cb793101df9961c3c746dfc476953dbbf5db337d8010dffd4e/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", size = 553152 }, + { url = "https://files.pythonhosted.org/packages/70/2d/5536d28c507a4679179ab15aa0049440e4d3dd6752050fa0843ed11e9354/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", size = 528807 }, + { url = "https://files.pythonhosted.org/packages/e3/62/7ebe6ec0d3dd6130921f8cffb7e34afb7f71b3819aa0446a24c5e81245ec/rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", size = 200993 }, + { url = "https://files.pythonhosted.org/packages/ec/2f/b938864d66b86a6e4acadefdc56de75ef56f7cafdfd568a6464605457bd5/rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", size = 214458 }, + { url = "https://files.pythonhosted.org/packages/99/32/43b919a0a423c270a838ac2726b1c7168b946f2563fd99a51aaa9692d00f/rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", size = 321465 }, + { url = "https://files.pythonhosted.org/packages/58/a9/c4d899cb28e9e47b0ff12462e8f827381f243176036f17bef9c1604667f2/rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", size = 312900 }, + { url = "https://files.pythonhosted.org/packages/8f/90/9e51670575b5dfaa8c823369ef7d943087bfb73d4f124a99ad6ef19a2b26/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", size = 370973 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/523f2a03f853fc0d4c1acbef161747e9ab7df0a8abf6236106e333540921/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", size = 370890 }, + { url = "https://files.pythonhosted.org/packages/51/ca/2458a771f16b0931de4d384decbe43016710bc948036c8f4562d6e063437/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", size = 397174 }, + { url = "https://files.pythonhosted.org/packages/00/7d/6e06807f6305ea2408b364efb0eef83a6e21b5e7b5267ad6b473b9a7e416/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", size = 426449 }, + { url = "https://files.pythonhosted.org/packages/8c/d1/6c9e65260a819a1714510a7d69ac1d68aa23ee9ce8a2d9da12187263c8fc/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", size = 357698 }, + { url = "https://files.pythonhosted.org/packages/5d/fb/ecea8b5286d2f03eec922be7173a03ed17278944f7c124348f535116db15/rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", size = 378530 }, + { url = "https://files.pythonhosted.org/packages/e3/e3/ac72f858957f52a109c588589b73bd2fad4a0fc82387fb55fb34aeb0f9cd/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", size = 545753 }, + { url = "https://files.pythonhosted.org/packages/b2/a4/a27683b519d5fc98e4390a3b130117d80fd475c67aeda8aac83c0e8e326a/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", size = 552443 }, + { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 }, + { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 }, + { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/81/4dfc17eb6ebb1aac314a3eb863c1325b907863a1b8b1382cdffcb6ac0ed9/ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b", size = 143362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/67/8ece580cc363331d9a53055130f86b096bf16e38156e33b1d3014fffda6b/ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", size = 117761 }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/ab/bab9eb1566cd16f060b54055dd39cf6a34bfa0240c53a7218c43e974295b/ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", size = 213824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/15/971b385c098e8d0d170893f5ba558452bb7b776a0c90658b8f4dd0e3382b/ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", size = 148870 }, + { url = "https://files.pythonhosted.org/packages/01/b0/4ddef56e9f703d7909febc3a421d709a3482cda25826816ec595b73e3847/ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", size = 134475 }, + { url = "https://files.pythonhosted.org/packages/a4/f7/22d6b620ed895a05d40802d8281eff924dc6190f682d933d4efff60db3b5/ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", size = 544020 }, + { url = "https://files.pythonhosted.org/packages/7c/e4/0d19d65e340f93df1c47f323d95fa4b256bb28320290f5fddef90837853a/ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", size = 642643 }, + { url = "https://files.pythonhosted.org/packages/c9/ff/f781eb5e2ae011e586d5426e2086a011cf1e0f59704a6cad1387975c5a62/ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", size = 695832 }, + { url = "https://files.pythonhosted.org/packages/e3/41/f62e67ac651358b8f0d60cfb12ab2daf99b1b69eeaa188d0cec809d943a6/ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9", size = 730923 }, + { url = "https://files.pythonhosted.org/packages/9f/f0/19ab8acbf983cd1b37f47d27ceb8b10a738d60d36316a54bad57e0d73fbb/ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", size = 99999 }, + { url = "https://files.pythonhosted.org/packages/ec/54/d8a795997921d87224c65d44499ca595a833093fb215b133f920c1062956/ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", size = 118008 }, + { url = "https://files.pythonhosted.org/packages/7a/a2/eb5e9d088cb9d15c24d956944c09dca0a89108ad6e2e913c099ef36e3f0d/ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", size = 144636 }, + { url = "https://files.pythonhosted.org/packages/66/98/8de4f22bbfd9135deb3422e96d450c4bc0a57d38c25976119307d2efe0aa/ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", size = 135684 }, + { url = "https://files.pythonhosted.org/packages/30/d3/5fe978cd01a61c12efd24d65fa68c6f28f28c8073a06cf11db3a854390ca/ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", size = 734571 }, + { url = "https://files.pythonhosted.org/packages/55/b3/e2531a050758b717c969cbf76c103b75d8a01e11af931b94ba656117fbe9/ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", size = 643946 }, + { url = "https://files.pythonhosted.org/packages/0d/aa/06db7ca0995b513538402e11280282c615b5ae5f09eb820460d35fb69715/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", size = 692169 }, + { url = "https://files.pythonhosted.org/packages/27/38/4cf4d482b84ecdf51efae6635cc5483a83cf5ca9d9c13e205a750e251696/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", size = 740325 }, + { url = "https://files.pythonhosted.org/packages/6f/67/c62c6eea53a4feb042727a3d6c18f50dc99683c2b199c06bd2a9e3db8e22/ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa", size = 98639 }, + { url = "https://files.pythonhosted.org/packages/10/d2/52a3d810d0b5b3720725c0504a27b3fced7b6f310fe928f7019d79387bc1/ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b", size = 115305 }, +] + +[[package]] +name = "semantic-kernel" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "azure-identity" }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "nest-asyncio" }, + { name = "numpy" }, + { name = "openai" }, + { name = "openapi-core" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "prance" }, + { name = "pybars4" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/8d/a2092be3cd98f7c9335a8878d1facd8ed551dfcc16dffd3a01634a64ebd0/semantic_kernel-1.11.0.tar.gz", hash = "sha256:7e1a42dbadee06b787254f0a222b1504174cf5cea83aaf454caaeeecd1b91b6b", size = 275014 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/85/197167f4a541eb3ee0bff3f5ccb43995b1680aeba49bc8bd7d38be700763/semantic_kernel-1.11.0-py3-none-any.whl", hash = "sha256:dabcf08394957921784f9a5c2bfe9cf3012aef43d0deaedfdb2fd0f6cd6d55e0", size = 472119 }, +] + [[package]] name = "semantic-workbench-api-model" version = "0.1.0" @@ -1796,6 +2306,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/27/96a5cd2626d11c8280656c6c71d8ab50fe006490ef9971ccd154e0c42cd2/websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f", size = 152134 }, ] +[[package]] +name = "werkzeug" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/e2/6dbcaab07560909ff8f654d3a2e5a60552d937c909455211b1b36d7101dc/werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306", size = 803966 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/84/997bbf7c2bf2dc3f09565c6d0b4959fefe5355c18c4096cfd26d83e0785b/werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", size = 227554 }, +] + +[[package]] +name = "wrapt" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/4c/063a912e20bcef7124e0df97282a8af3ff3e4b603ce84c481d6d7346be0a/wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d", size = 53972 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/03/c188ac517f402775b90d6f312955a5e53b866c964b32119f2ed76315697e/wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09", size = 37313 }, + { url = "https://files.pythonhosted.org/packages/0f/16/ea627d7817394db04518f62934a5de59874b587b792300991b3c347ff5e0/wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d", size = 38164 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/f1212ba098f3de0fd244e2de0f8791ad2539c03bef6c05a9fcb03e45b089/wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389", size = 80890 }, + { url = "https://files.pythonhosted.org/packages/b7/96/bb5e08b3d6db003c9ab219c487714c13a237ee7dcc572a555eaf1ce7dc82/wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060", size = 73118 }, + { url = "https://files.pythonhosted.org/packages/6e/52/2da48b35193e39ac53cfb141467d9f259851522d0e8c87153f0ba4205fb1/wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1", size = 80746 }, + { url = "https://files.pythonhosted.org/packages/11/fb/18ec40265ab81c0e82a934de04596b6ce972c27ba2592c8b53d5585e6bcd/wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3", size = 85668 }, + { url = "https://files.pythonhosted.org/packages/0f/ef/0ecb1fa23145560431b970418dce575cfaec555ab08617d82eb92afc7ccf/wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956", size = 78556 }, + { url = "https://files.pythonhosted.org/packages/25/62/cd284b2b747f175b5a96cbd8092b32e7369edab0644c45784871528eb852/wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d", size = 85712 }, + { url = "https://files.pythonhosted.org/packages/e5/a7/47b7ff74fbadf81b696872d5ba504966591a3468f1bc86bca2f407baef68/wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362", size = 35327 }, + { url = "https://files.pythonhosted.org/packages/cf/c3/0084351951d9579ae83a3d9e38c140371e4c6b038136909235079f2e6e78/wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89", size = 37523 }, + { url = "https://files.pythonhosted.org/packages/92/17/224132494c1e23521868cdd57cd1e903f3b6a7ba6996b7b8f077ff8ac7fe/wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b", size = 37614 }, + { url = "https://files.pythonhosted.org/packages/6a/d7/cfcd73e8f4858079ac59d9db1ec5a1349bc486ae8e9ba55698cc1f4a1dff/wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36", size = 38316 }, + { url = "https://files.pythonhosted.org/packages/7e/79/5ff0a5c54bda5aec75b36453d06be4f83d5cd4932cc84b7cb2b52cee23e2/wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73", size = 86322 }, + { url = "https://files.pythonhosted.org/packages/c4/81/e799bf5d419f422d8712108837c1d9bf6ebe3cb2a81ad94413449543a923/wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809", size = 79055 }, + { url = "https://files.pythonhosted.org/packages/62/62/30ca2405de6a20448ee557ab2cd61ab9c5900be7cbd18a2639db595f0b98/wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b", size = 87291 }, + { url = "https://files.pythonhosted.org/packages/49/4e/5d2f6d7b57fc9956bf06e944eb00463551f7d52fc73ca35cfc4c2cdb7aed/wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81", size = 90374 }, + { url = "https://files.pythonhosted.org/packages/a6/9b/c2c21b44ff5b9bf14a83252a8b973fb84923764ff63db3e6dfc3895cf2e0/wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9", size = 83896 }, + { url = "https://files.pythonhosted.org/packages/14/26/93a9fa02c6f257df54d7570dfe8011995138118d11939a4ecd82cb849613/wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c", size = 91738 }, + { url = "https://files.pythonhosted.org/packages/a2/5b/4660897233eb2c8c4de3dc7cefed114c61bacb3c28327e64150dc44ee2f6/wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc", size = 35568 }, + { url = "https://files.pythonhosted.org/packages/5c/cc/8297f9658506b224aa4bd71906447dea6bb0ba629861a758c28f67428b91/wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8", size = 37653 }, + { url = "https://files.pythonhosted.org/packages/ff/21/abdedb4cdf6ff41ebf01a74087740a709e2edb146490e4d9beea054b0b7a/wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1", size = 23362 }, +] + [[package]] name = "yarl" version = "1.13.1" @@ -1853,3 +2404,12 @@ wheels = [ { 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 }, ] + +[[package]] +name = "zipp" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200 }, +] diff --git a/assistants/skill-assistant/assistant.code-workspace b/assistants/skill-assistant/assistant.code-workspace new file mode 100644 index 0000000..2422973 --- /dev/null +++ b/assistants/skill-assistant/assistant.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": ".", + "name": "assistants/skill-assistant" + }, + { + "path": "../.." + } + ] +} diff --git a/docs/SETUP_DEV_ENVIRONMENT.md b/docs/SETUP_DEV_ENVIRONMENT.md index 127487d..873aace 100644 --- a/docs/SETUP_DEV_ENVIRONMENT.md +++ b/docs/SETUP_DEV_ENVIRONMENT.md @@ -73,37 +73,10 @@ If you haven't already, enable long file paths on Windows. - Find the `LongPathsEnabled` key. If it doesn’t exist, right-click on the `FileSystem` key, select `New > DWORD (32-bit) Value`, and name it `LongPathsEnabled`. - Double-click on `LongPathsEnabled`, set its value to `1`, and click OK. -### Configure and build the backend +# Install and run the backend -# Frontend Setup +See [workbench-service/README.md](../workbench-service/README.md) -The frontend for the Semantic Workbench is written in [Node 20](https://nodejs.org/en/download). +# Install and run the frontend -Linux ([latest instructions](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating)): - - curl -o- https://github.com/raw/nvm-sh/nvm/v0.40.1/install.sh | bash - -macOS: - - brew install nvm - -Windows: - - winget install CoreyButler.NVMforWindows - -Once you have nvm installed: - -``` -nvm install 20 lts -nvm use 20 -npm install -g pnpm -``` - -### Build the frontend - -Within the [`v1/app`](../workbench-app/) directory, install packages. - -``` -cd workbench-app -make -``` +See [workbench-app/README.md](../workbench-app/README.md) diff --git a/examples/dotnet/dotnet-02-message-types-demo/Program.cs b/examples/dotnet/dotnet-02-message-types-demo/Program.cs index 3f8d988..bbbb1f5 100644 --- a/examples/dotnet/dotnet-02-message-types-demo/Program.cs +++ b/examples/dotnet/dotnet-02-message-types-demo/Program.cs @@ -54,12 +54,11 @@ private static IServiceCollection AddAzureAIContentSafety( IConfiguration config) { var authType = config.GetValue("AuthType"); - var endpoint = config.GetValue("Endpoint"); + var endpoint = new Uri(config.GetValue("Endpoint")!); var apiKey = config.GetValue("ApiKey"); return services.AddSingleton(_ => authType == "AzureIdentity" - ? new ContentSafetyClient(new Uri(endpoint!), new DefaultAzureCredential()) - : new ContentSafetyClient(new Uri(endpoint!), - new AzureKeyCredential(apiKey!))); + ? new ContentSafetyClient(endpoint, new DefaultAzureCredential()) + : new ContentSafetyClient(endpoint, new AzureKeyCredential(apiKey!))); } } diff --git a/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs b/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs index fd905a8..ae1597f 100644 --- a/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs +++ b/examples/dotnet/dotnet-03-simple-chatbot/MyAgentConfig.cs @@ -5,7 +5,7 @@ namespace AgentExample; -public class MyAgentConfig : IAgentConfig +public class MyAgentConfig : AgentConfig { // Define safety and behavioral guardrails. // See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message for more information and examples. @@ -22,198 +22,93 @@ public class MyAgentConfig : IAgentConfig [JsonPropertyName(nameof(this.SystemPromptSafety))] [JsonPropertyOrder(0)] + [AgentConfigProperty("type", "string")] + [AgentConfigProperty("title", "Safety guardrails")] + [AgentConfigProperty("description", "Instructions used to define safety and behavioral guardrails. See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message.")] + [AgentConfigProperty("maxLength", 2048)] + [AgentConfigProperty("default", DefaultPromptSafety)] + [AgentConfigProperty("uischema", "textarea")] public string SystemPromptSafety { get; set; } = DefaultPromptSafety; [JsonPropertyName(nameof(this.SystemPrompt))] [JsonPropertyOrder(1)] + [AgentConfigProperty("type", "string")] + [AgentConfigProperty("title", "System prompt")] + [AgentConfigProperty("description", "Initial system message used to define the assistant behavior.")] + [AgentConfigProperty("maxLength", 32768)] + [AgentConfigProperty("default", DefaultSystemPrompt)] + [AgentConfigProperty("uischema", "textarea")] public string SystemPrompt { get; set; } = DefaultSystemPrompt; [JsonPropertyName(nameof(this.ReplyToAgents))] [JsonPropertyOrder(10)] + [AgentConfigProperty("type", "boolean")] + [AgentConfigProperty("title", "Reply to other assistants in conversations")] + [AgentConfigProperty("description", "Reply to assistants")] + [AgentConfigProperty("default", false)] public bool ReplyToAgents { get; set; } = false; [JsonPropertyName(nameof(this.CommandsEnabled))] [JsonPropertyOrder(20)] + [AgentConfigProperty("type", "boolean")] + [AgentConfigProperty("title", "Support commands")] + [AgentConfigProperty("description", "Support commands, e.g. /say")] + [AgentConfigProperty("default", false)] public bool CommandsEnabled { get; set; } = false; [JsonPropertyName(nameof(this.MaxMessagesCount))] [JsonPropertyOrder(30)] + [AgentConfigProperty("type", "integer")] + [AgentConfigProperty("title", "Max conversation messages")] + [AgentConfigProperty("description", "How many messages to answer in a conversation before ending and stopping replies.")] + [AgentConfigProperty("minimum", 1)] + [AgentConfigProperty("maximum", int.MaxValue)] + [AgentConfigProperty("default", 100)] public int MaxMessagesCount { get; set; } = 100; [JsonPropertyName(nameof(this.Temperature))] [JsonPropertyOrder(40)] + [AgentConfigProperty("type", "number")] + [AgentConfigProperty("title", "LLM temperature")] + [AgentConfigProperty("description", "The temperature value ranges from 0 to 1. Lower values indicate greater determinism and higher values indicate more randomness.")] + [AgentConfigProperty("minimum", 0.0)] + [AgentConfigProperty("maximum", 1.0)] + [AgentConfigProperty("default", 0.0)] public double Temperature { get; set; } = 0.0; [JsonPropertyName(nameof(this.NucleusSampling))] [JsonPropertyOrder(50)] + [AgentConfigProperty("type", "number")] + [AgentConfigProperty("title", "LLM nucleus sampling")] + [AgentConfigProperty("description", "Nucleus sampling probability ranges from 0 to 1. Lower values result in more deterministic outputs by limiting the choice to the most probable words, and higher values allow for more randomness by including a larger set of potential words.")] + [AgentConfigProperty("minimum", 0.0)] + [AgentConfigProperty("maximum", 1.0)] + [AgentConfigProperty("default", 1.0)] public double NucleusSampling { get; set; } = 1.0; [JsonPropertyName(nameof(this.LLMProvider))] [JsonPropertyOrder(60)] + [AgentConfigProperty("type", "string")] + [AgentConfigProperty("default", "openai")] + [AgentConfigProperty("enum", new[] { "openai", "azure-openai" })] + [AgentConfigProperty("title", "LLM provider")] + [AgentConfigProperty("description", "AI service providing the LLM.")] + [AgentConfigProperty("uischema", "radiobutton")] public string LLMProvider { get; set; } = "openai"; - // [JsonPropertyName(nameof(this.LLMEndpoint))] - // [JsonPropertyOrder(70)] - // public string LLMEndpoint { get; set; } = "https://api.openai.com/v1"; - [JsonPropertyName(nameof(this.ModelName))] [JsonPropertyOrder(80)] + [AgentConfigProperty("type", "string")] + [AgentConfigProperty("title", "OpenAI Model (or Azure Deployment)")] + [AgentConfigProperty("description", "Model used to generate text.")] + [AgentConfigProperty("maxLength", 256)] + [AgentConfigProperty("default", "GPT-4o")] public string ModelName { get; set; } = "gpt-4o"; - public void Update(object? config) - { - if (config == null) - { - throw new ArgumentException("Incompatible or empty configuration"); - } - - if (config is not MyAgentConfig cfg) - { - throw new ArgumentException("Incompatible configuration type"); - } - - this.SystemPrompt = cfg.SystemPrompt; - this.SystemPromptSafety = cfg.SystemPromptSafety; - this.ReplyToAgents = cfg.ReplyToAgents; - this.CommandsEnabled = cfg.CommandsEnabled; - this.MaxMessagesCount = cfg.MaxMessagesCount; - this.Temperature = cfg.Temperature; - this.NucleusSampling = cfg.NucleusSampling; - this.LLMProvider = cfg.LLMProvider; - // this.LLMEndpoint = cfg.LLMEndpoint; - this.ModelName = cfg.ModelName; - } - public string RenderSystemPrompt() { return string.IsNullOrWhiteSpace(this.SystemPromptSafety) ? this.SystemPrompt : $"{this.SystemPromptSafety}\n{this.SystemPrompt}"; } - - public object ToWorkbenchFormat() - { - Dictionary result = new(); - Dictionary defs = new(); - Dictionary properties = new(); - Dictionary jsonSchema = new(); - Dictionary uiSchema = new(); - - // AI Safety configuration. See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message - properties[nameof(this.SystemPromptSafety)] = new Dictionary - { - { "type", "string" }, - { "title", "Safety guardrails" }, - { - "description", - "Instructions used to define safety and behavioral guardrails. See https://learn.microsoft.com/azure/ai-services/openai/concepts/system-message." - }, - { "maxLength", 2048 }, - { "default", DefaultPromptSafety } - }; - ConfigUtils.UseTextAreaFor(nameof(this.SystemPromptSafety), uiSchema); - - // Initial AI instructions, aka System prompt or Meta-prompt. - properties[nameof(this.SystemPrompt)] = new Dictionary - { - { "type", "string" }, - { "title", "System prompt" }, - { "description", "Initial system message used to define the assistant behavior." }, - { "maxLength", 32768 }, - { "default", DefaultSystemPrompt } - }; - ConfigUtils.UseTextAreaFor(nameof(this.SystemPrompt), uiSchema); - - properties[nameof(this.ReplyToAgents)] = new Dictionary - { - { "type", "boolean" }, - { "title", "Reply to other assistants in conversations" }, - { "description", "Reply to assistants" }, - { "default", false } - }; - - properties[nameof(this.CommandsEnabled)] = new Dictionary - { - { "type", "boolean" }, - { "title", "Support commands" }, - { "description", "Support commands, e.g. /say" }, - { "default", false } - }; - - properties[nameof(this.MaxMessagesCount)] = new Dictionary - { - { "type", "integer" }, - { "title", "Max conversation messages" }, - { "description", "How many messages to answer in a conversation before ending and stopping replies." }, - { "minimum", 1 }, - { "maximum", int.MaxValue }, - { "default", 100 } - }; - - properties[nameof(this.Temperature)] = new Dictionary - { - { "type", "number" }, - { "title", "LLM temperature" }, - { - "description", - "The temperature value ranges from 0 to 1. Lower values indicate greater determinism and higher values indicate more randomness." - }, - { "minimum", 0.0 }, - { "maximum", 1.0 }, - { "default", 0.0 } - }; - - properties[nameof(this.NucleusSampling)] = new Dictionary - { - { "type", "number" }, - { "title", "LLM nucleus sampling" }, - { - "description", - "Nucleus sampling probability ranges from 0 to 1. Lower values result in more deterministic outputs by limiting the choice to the most probable words, and higher values allow for more randomness by including a larger set of potential words." - }, - { "minimum", 0.0 }, - { "maximum", 1.0 }, - { "default", 1.0 } - }; - - properties[nameof(this.LLMProvider)] = new Dictionary - { - { "type", "string" }, - { "default", "openai" }, - { "enum", new[] { "openai", "azure-openai" } }, - { "title", "LLM provider" }, - { "description", "AI service providing the LLM." }, - }; - ConfigUtils.UseRadioButtonsFor(nameof(this.LLMProvider), uiSchema); - - // properties[nameof(this.LLMEndpoint)] = new Dictionary - // { - // { "type", "string" }, - // { "title", "LLM endpoint" }, - // { "description", "OpenAI/Azure OpenAI endpoint." }, - // { "maxLength", 256 }, - // { "default", "https://api.openai.com/v1" } - // }; - - properties[nameof(this.ModelName)] = new Dictionary - { - { "type", "string" }, - { "title", "OpenAI Model (or Azure Deployment)" }, - { "description", "Model used to generate text." }, - { "maxLength", 256 }, - { "default", "GPT-4o" } - }; - - jsonSchema["type"] = "object"; - jsonSchema["title"] = "ConfigStateModel"; - jsonSchema["additionalProperties"] = false; - jsonSchema["properties"] = properties; - jsonSchema["$defs"] = defs; - - result["json_schema"] = jsonSchema; - result["ui_schema"] = uiSchema; - result["config"] = this; - - return result; - } } diff --git a/examples/dotnet/dotnet-03-simple-chatbot/Program.cs b/examples/dotnet/dotnet-03-simple-chatbot/Program.cs index 61bc62f..9a9a328 100644 --- a/examples/dotnet/dotnet-03-simple-chatbot/Program.cs +++ b/examples/dotnet/dotnet-03-simple-chatbot/Program.cs @@ -48,13 +48,12 @@ private static IServiceCollection AddAzureAIContentSafety( IConfiguration config) { var authType = config.GetValue("AuthType"); - var endpoint = config.GetValue("Endpoint"); + var endpoint = new Uri(config.GetValue("Endpoint")!); var apiKey = config.GetValue("ApiKey"); return services.AddSingleton(_ => authType == "AzureIdentity" - ? new ContentSafetyClient(new Uri(endpoint!), new DefaultAzureCredential()) - : new ContentSafetyClient(new Uri(endpoint!), - new AzureKeyCredential(apiKey!))); + ? new ContentSafetyClient(endpoint, new DefaultAzureCredential()) + : new ContentSafetyClient(endpoint, new AzureKeyCredential(apiKey!))); } /* diff --git a/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj b/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj index 0768b4b..6e47ae5 100644 --- a/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj +++ b/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj @@ -13,7 +13,6 @@ - @@ -57,4 +56,9 @@ + + + + \ No newline at end of file diff --git a/examples/python/python-01-echo-bot/assistant.code-workspace b/examples/python/python-01-echo-bot/assistant.code-workspace index 2318eb7..66a419d 100644 --- a/examples/python/python-01-echo-bot/assistant.code-workspace +++ b/examples/python/python-01-echo-bot/assistant.code-workspace @@ -5,7 +5,7 @@ "name": "examples/python/python-01-echo-bot" }, { - "path": "../../" + "path": "../../.." } ] } diff --git a/examples/python/python-02-simple-chatbot/assistant.code-workspace b/examples/python/python-02-simple-chatbot/assistant.code-workspace index 817ba33..36177e1 100644 --- a/examples/python/python-02-simple-chatbot/assistant.code-workspace +++ b/examples/python/python-02-simple-chatbot/assistant.code-workspace @@ -5,7 +5,7 @@ "name": "examples/python/python-02-simple-chatbot" }, { - "path": "../.." + "path": "../../.." } ] } diff --git a/examples/python/python-03-multimodel-chatbot/assistant.code-workspace b/examples/python/python-03-multimodel-chatbot/assistant.code-workspace index f1b47c1..29ffe19 100644 --- a/examples/python/python-03-multimodel-chatbot/assistant.code-workspace +++ b/examples/python/python-03-multimodel-chatbot/assistant.code-workspace @@ -5,7 +5,7 @@ "name": "examples/python/python-03-multimodel-chatbot" }, { - "path": "../.." + "path": "../../.." } ] } diff --git a/libraries/dotnet/SemanticWorkbench.sln.DotSettings b/libraries/dotnet/SemanticWorkbench.sln.DotSettings index 67d5772..8732803 100644 --- a/libraries/dotnet/SemanticWorkbench.sln.DotSettings +++ b/libraries/dotnet/SemanticWorkbench.sln.DotSettings @@ -4,4 +4,5 @@ CORS HTML JSON - LLM \ No newline at end of file + LLM + UI \ No newline at end of file diff --git a/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs new file mode 100644 index 0000000..0c8d982 --- /dev/null +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfig.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Reflection; + +// ReSharper disable once CheckNamespace +namespace Microsoft.SemanticWorkbench.Connector; + +public class AgentConfig : IAgentConfig +{ + public object? ToWorkbenchFormat() + { + Dictionary result = new(); + Dictionary defs = new(); + Dictionary properties = new(); + Dictionary jsonSchema = new(); + Dictionary uiSchema = new(); + + foreach (var property in this.GetType().GetProperties()) + { + var config = new Dictionary(); + var attributes = property.GetCustomAttributes(); + foreach (var attribute in attributes) + { + config[attribute.Name] = attribute.Value; + } + + properties[property.Name] = config; + + if (config.TryGetValue("uischema", out var uiSchemaValue)) + { + switch (uiSchemaValue) + { + case "textarea": + ConfigUtils.UseTextAreaFor(property.Name, uiSchema); + break; + case "radiobutton": + ConfigUtils.UseRadioButtonsFor(property.Name, uiSchema); + break; + default: + break; + } + } + } + + jsonSchema["type"] = "object"; + jsonSchema["title"] = "ConfigStateModel"; + jsonSchema["additionalProperties"] = false; + jsonSchema["properties"] = properties; + jsonSchema["$defs"] = defs; + + result["json_schema"] = jsonSchema; + result["ui_schema"] = uiSchema; + result["config"] = this; + + return result; + } + + public void Update(object? config) + { + if (config == null) + { + throw new ArgumentException("Empty configuration"); + } + + if (config is not AgentConfig cfg) + { + throw new ArgumentException("Incompatible configuration type"); + } + + foreach (var property in this.GetType().GetProperties()) + { + property.SetValue(this, property.GetValue(cfg)); + } + } +} diff --git a/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfigPropertyAttribute.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfigPropertyAttribute.cs new file mode 100644 index 0000000..5d38aa6 --- /dev/null +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/AgentConfigPropertyAttribute.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; + +// ReSharper disable once CheckNamespace +namespace Microsoft.SemanticWorkbench.Connector; + +[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] +public class AgentConfigPropertyAttribute : Attribute +{ + public string Name { get; } + public object Value { get; } + + public AgentConfigPropertyAttribute(string name, object value) + { + this.Name = name; + this.Value = value; + } +} diff --git a/libraries/dotnet/WorkbenchConnector/ConfigUtils.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/ConfigUtils.cs similarity index 94% rename from libraries/dotnet/WorkbenchConnector/ConfigUtils.cs rename to libraries/dotnet/WorkbenchConnector/AgentConfig/ConfigUtils.cs index 7c4f7c4..d5194c2 100644 --- a/libraries/dotnet/WorkbenchConnector/ConfigUtils.cs +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/ConfigUtils.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; +// ReSharper disable once CheckNamespace namespace Microsoft.SemanticWorkbench.Connector; public static class ConfigUtils diff --git a/libraries/dotnet/WorkbenchConnector/IAgentConfig.cs b/libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs similarity index 84% rename from libraries/dotnet/WorkbenchConnector/IAgentConfig.cs rename to libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs index c4f0122..5f0f591 100644 --- a/libraries/dotnet/WorkbenchConnector/IAgentConfig.cs +++ b/libraries/dotnet/WorkbenchConnector/AgentConfig/IAgentConfig.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +// ReSharper disable once CheckNamespace namespace Microsoft.SemanticWorkbench.Connector; public interface IAgentConfig diff --git a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj index 34719fc..ef9c5ee 100644 --- a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj +++ b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.csproj @@ -1,7 +1,7 @@  - 0.1.0 + 0.2.0 Microsoft.SemanticWorkbench.Connector Microsoft.SemanticWorkbench.Connector net8.0 @@ -9,7 +9,7 @@ enable 12 LatestMajor - $(NoWarn);IDE0130;CA2254;CA1812; + $(NoWarn);IDE0130;CA2254;CA1812;CA1813; @@ -107,4 +107,4 @@ - + \ No newline at end of file diff --git a/libraries/python/assistant-drive/assistant_drive/drive.py b/libraries/python/assistant-drive/assistant_drive/drive.py index e4d79a6..c44dfe2 100644 --- a/libraries/python/assistant-drive/assistant_drive/drive.py +++ b/libraries/python/assistant-drive/assistant_drive/drive.py @@ -1,3 +1,4 @@ +import io import json import logging import os @@ -5,7 +6,7 @@ from contextlib import contextmanager from datetime import datetime from shutil import rmtree -from typing import BinaryIO, Iterator +from typing import Any, BinaryIO, ContextManager, Iterator, TypeVar import magic from context import Context @@ -147,7 +148,7 @@ def add_bytes( return file.metadata - def delete(self, dir: str | None = None, filename: str | None = None) -> None: + def delete(self, filename: str | None = None, dir: str | None = None) -> None: file_path = self._path_for(filename, dir) if file_path.is_file(): file_path.unlink() @@ -161,14 +162,24 @@ def delete(self, dir: str | None = None, filename: str | None = None) -> None: if metadata_path.is_dir(): rmtree(metadata_path) - def read_all_files(self, dir: str) -> Iterator[BinaryIO]: - dir_path = self._path_for("", dir) + def open_files(self, dir: str | None = None) -> Iterator[ContextManager[BinaryIO]]: + dir_path = self._path_for(None, dir) if not dir_path.is_dir(): return - for file_path in dir_path.iterdir(): - with open(file_path, "rb") as f: + @contextmanager + def open_file(file_path: pathlib.Path) -> Iterator[BinaryIO]: + f = open(file_path, "rb") + try: yield f + finally: + f.close() + + for file_path in dir_path.iterdir(): + # Don't include directories + if file_path.is_dir(): + continue + yield open_file(file_path) def list(self, dir: str = "") -> Iterator[str]: dir_path = self._path_for("", dir) @@ -182,7 +193,7 @@ def list(self, dir: str = "") -> Iterator[str]: yield file_path.name @contextmanager - def read_file(self, filename: str, dir: str | None = None) -> Iterator[BinaryIO]: + def open_file(self, filename: str, dir: str | None = None) -> Iterator[BinaryIO]: file_path = self._path_for(filename, dir) with open(file_path, "rb") as f: yield f @@ -190,3 +201,41 @@ def read_file(self, filename: str, dir: str | None = None) -> Iterator[BinaryIO] def file_exists(self, filename: str, dir: str | None = None) -> bool: file_path = self._path_for(filename, dir) return file_path.exists() + + def write_model( + self, + value: BaseModel, + filename: str, + dir: str | None = None, + serialization_context: dict[str, Any] | None = None, + overwrite: bool = False, + ) -> None: + """Write a pydantic model to a file.""" + data_json = value.model_dump_json(context=serialization_context) + data_bytes = data_json.encode("utf-8") + self.add_bytes(io.BytesIO(data_bytes), filename, dir, overwrite) + + ModelT = TypeVar("ModelT", bound=BaseModel) + + def read_model( + self, cls: type[ModelT], filename: str, dir: str | None = None, strict: bool | None = None + ) -> ModelT: + """Read a pydantic model from a file.""" + with self.open_file(filename, dir) as f: + data_json = f.read().decode("utf-8") + + return cls.model_validate_json(data_json, strict=strict) + + def read_models(self, cls: type[ModelT], dir: str | None = None) -> Iterator[ModelT]: + """Read pydantic models from all files in a directory.""" + dir_path = self._path_for(None, dir) + if not dir_path.is_dir(): + return + + for file_path in dir_path.iterdir(): + # Don't include directories + if file_path.is_dir(): + continue + value = self.read_model(cls, file_path.name, dir) + if value is not None: + yield value diff --git a/libraries/python/assistant-drive/assistant_drive/tests/test_basic.py b/libraries/python/assistant-drive/assistant_drive/tests/test_basic.py index 577d4ae..6f6fe07 100644 --- a/libraries/python/assistant-drive/assistant_drive/tests/test_basic.py +++ b/libraries/python/assistant-drive/assistant_drive/tests/test_basic.py @@ -3,6 +3,7 @@ import pytest from assistant_drive import Drive, DriveConfig from context import Context +from pydantic import BaseModel file_content = BytesIO(b"Hello, World!") # Convert byte string to BytesIO @@ -68,9 +69,9 @@ def test_exists(drive): assert drive.file_exists("test.txt", "summaries") -def test_read(drive): +def test_open(drive): drive.add_bytes(file_content, "test.txt", "summaries") - with drive.read_file("test.txt", "summaries") as f: + with drive.open_file("test.txt", "summaries") as f: assert f.read() == b"Hello, World!" @@ -82,9 +83,9 @@ def test_list(drive): assert sorted(list(drive.list(dir="summaries"))) == ["test.txt", "test2.txt"] -def test_read_non_existent_file(drive): +def test_open_non_existent_file(drive): with pytest.raises(FileNotFoundError): - with drive.read_file("test.txt", "summaries") as f: + with drive.open_file("test.txt", "summaries") as f: f.read() @@ -99,7 +100,7 @@ def test_overwrite(drive): drive.add_bytes(file_content, "test.txt", "summaries") metadata = drive.add_bytes(BytesIO(b"XXX"), "test.txt", "summaries", overwrite=True) assert metadata.filename == "test.txt" - with drive.read_file("test.txt", "summaries") as f: + with drive.open_file("test.txt", "summaries") as f: assert f.read() == b"XXX" assert list(drive.list(dir="summaries")) == ["test.txt"] @@ -115,3 +116,73 @@ def test_delete(drive): assert sorted(list(drive.list(dir="summaries"))) == sorted(["test.txt"]) drive.delete() + + +def test_open_files_multiple_files(drive) -> None: + drive.add_bytes(file_content, "test.txt", "summaries") + drive.add_bytes(file_content, "test2.txt", "summaries") + + files = list(drive.open_files("summaries")) + assert len(files) == 2 + + for file_context in files: + with file_context as file: + assert file.read() == b"Hello, World!" + + +def test_open_files_empty_dir(drive) -> None: + files = list(drive.open_files("summaries")) + assert len(files) == 0 + + +def test_write_model(drive) -> None: + class TestModel(BaseModel): + name: str + + model = TestModel(name="test") + drive.write_model(model, "test.json", "summaries") + + with drive.open_file("test.json", "summaries") as f: + assert f.read() == b'{"name":"test"}' + + drive.delete() + + +def test_read_model(drive) -> None: + class TestModel(BaseModel): + name: str + + model = TestModel(name="test") + drive.write_model(model, "test.json", "summaries") + + model = drive.read_model(TestModel, "test.json", "summaries") + assert model.name == "test" + + drive.delete() + + +def test_read_models(drive) -> None: + class TestModel(BaseModel): + name: str + + model_1 = TestModel(name="test1") + drive.write_model(model_1, "test1.json", "summaries") + + model_2 = TestModel(name="test2") + drive.write_model(model_2, "test2.json", "summaries") + + models = list(drive.read_models(TestModel, "summaries")) + assert len(models) == 2 + + assert models[0].name == "test1" + assert models[1].name == "test2" + + +def test_read_model_non_existent_file(drive) -> None: + class TestModel(BaseModel): + name: str + + with pytest.raises(FileNotFoundError): + drive.read_model(TestModel, "test.json", "summaries") + + drive.delete() diff --git a/libraries/python/semantic-workbench-api-model/semantic_workbench_api_model/assistant_model.py b/libraries/python/semantic-workbench-api-model/semantic_workbench_api_model/assistant_model.py index 9e60a2e..be8b693 100644 --- a/libraries/python/semantic-workbench-api-model/semantic_workbench_api_model/assistant_model.py +++ b/libraries/python/semantic-workbench-api-model/semantic_workbench_api_model/assistant_model.py @@ -42,7 +42,7 @@ class StatePutRequestModel(BaseModel): class ConfigResponseModel(BaseModel): config: dict[str, Any] - errors: list[str] | None + errors: list[str] | None = None json_schema: dict[str, Any] | None ui_schema: dict[str, Any] | None diff --git a/semantic-workbench.code-workspace b/semantic-workbench.code-workspace index 7c838f0..7453cee 100644 --- a/semantic-workbench.code-workspace +++ b/semantic-workbench.code-workspace @@ -143,4 +143,4 @@ "settings": { "markdown.validate.enabled": true } -} \ No newline at end of file +} diff --git a/tools/docker/Dockerfile.assistant b/tools/docker/Dockerfile.assistant index 65ca1f7..9e41ee8 100644 --- a/tools/docker/Dockerfile.assistant +++ b/tools/docker/Dockerfile.assistant @@ -27,6 +27,20 @@ FROM ${python_image} ARG app +# BEGIN: enable ssh in azure web app - comment out if not needed +######## +# install sshd and set password for root +RUN apt-get update && apt-get install -y --no-install-recommends \ + openssh-server \ + && rm -rf /var/lib/apt/lists/* \ + && echo "root:Docker!" | chpasswd + +# azure sshd config +COPY ./tools/docker/azure_website_sshd.conf /etc/ssh/sshd_config +ENV SSHD_PORT=2222 +######## +# END: enable ssh in azure web app + COPY --from=build /packages/assistants/assistant/.venv /packages/assistants/assistant/.venv ENV PATH=/packages/assistants/assistant/.venv/bin:$PATH diff --git a/tools/makefiles/docker.mk b/tools/makefiles/docker.mk index 0870f77..dd08df6 100644 --- a/tools/makefiles/docker.mk +++ b/tools/makefiles/docker.mk @@ -1,6 +1,7 @@ DOCKER_REGISTRY_NAME ?= +ifneq ($(DOCKER_REGISTRY_NAME),) DOCKER_REGISTRY_HOST ?= $(DOCKER_REGISTRY_NAME).azurecr.io -DOCKER_USERNAME ?= $(DOCKER_REGISTRY_NAME) +endif DOCKER_IMAGE_TAG ?= $(shell git rev-parse HEAD) DOCKER_PUSH_LATEST ?= true DOCKER_PATH ?= . diff --git a/workbench-app/src/routes/AssistantEditor.tsx b/workbench-app/src/routes/AssistantEditor.tsx index 7f8c573..305f3e8 100644 --- a/workbench-app/src/routes/AssistantEditor.tsx +++ b/workbench-app/src/routes/AssistantEditor.tsx @@ -3,7 +3,7 @@ import { useAccount } from '@azure/msal-react'; import { Title3, Toolbar, makeStyles, shorthands, tokens } from '@fluentui/react-components'; import React from 'react'; -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { AppView } from '../components/App/AppView'; import { Loading } from '../components/App/Loading'; import { AssistantDelete } from '../components/Assistants/AssistantDelete'; @@ -70,6 +70,7 @@ export const AssistantEditor: React.FC = () => { const [createConversationMessage] = useCreateConversationMessageMutation(); const account = useAccount(); const siteUtility = useSiteUtility(); + const navigate = useNavigate(); if (assistantConversationsError) { const errorMessage = JSON.stringify(assistantConversationsError); @@ -128,6 +129,9 @@ export const AssistantEditor: React.FC = () => { // add assistant to conversation await addConversationParticipant({ conversationId: conversation.id, participantId: assistantId }); + + // navigate to conversation + navigate(`/conversation/${conversation.id}`); }; if (isLoadingAssistant || isLoadingAssistantConversations || !assistant) { diff --git a/workbench-app/src/routes/Dashboard.tsx b/workbench-app/src/routes/Dashboard.tsx index e7829d6..a78d2af 100644 --- a/workbench-app/src/routes/Dashboard.tsx +++ b/workbench-app/src/routes/Dashboard.tsx @@ -2,6 +2,7 @@ import { makeStyles, tokens } from '@fluentui/react-components'; import React from 'react'; +import { useNavigate } from 'react-router-dom'; import { AppMenu } from '../components/App/AppMenu'; import { AppView } from '../components/App/AppView'; import { ExperimentalNotice } from '../components/App/ExperimentalNotice'; @@ -11,6 +12,7 @@ import { MyConversations } from '../components/Conversations/MyConversations'; import { MyWorkflows } from '../components/Workflows/MyWorkflows'; import { useLocalUserAccount } from '../libs/useLocalUserAccount'; import { useSiteUtility } from '../libs/useSiteUtility'; +import { Conversation } from '../models/Conversation'; import { useGetAssistantsQuery, useGetConversationsQuery } from '../services/workbench'; import { useGetWorkflowDefinitionsQuery } from '../services/workbench/workflow'; @@ -36,6 +38,7 @@ export const Dashboard: React.FC = () => { isLoading: isLoadingWorkflowDefinitions, } = useGetWorkflowDefinitionsQuery(); const { getUserId } = useLocalUserAccount(); + const navigate = useNavigate(); const siteUtility = useSiteUtility(); siteUtility.setDocumentTitle('Dashboard'); @@ -55,6 +58,13 @@ export const Dashboard: React.FC = () => { throw new Error(`Error loading workflow definitions: ${errorMessage}`); } + const handleConversationCreate = React.useCallback( + (conversation: Conversation) => { + navigate(`/conversation/${conversation.id}`); + }, + [navigate], + ); + const appMenuAction = ; if (isLoadingAssistants || isLoadingConversations || isLoadingWorkflowDefinitions) { @@ -74,12 +84,17 @@ export const Dashboard: React.FC = () => {
- + {conversationsSharedWithMe.length > 0 && ( )}