{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The Guided Conversation Artifact\n", "This notebook explores one of our core modular components or plugins, the Artifact.\n", "\n", "The artifact is a form, or a type of working memory for the agent. We implement it using a Pydantic BaseModel. As the conversation creator, you can define an arbitrary BaseModel that includes the fields you want the agent to fill out during the conversation. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Motivating Example - Collecting Information from a User\n", "\n", "Let's setup an artifact where the goal is to collect information about a customer's issue with a service." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "from typing import Literal\n", "\n", "from pydantic import BaseModel, Field, conlist\n", "\n", "\n", "class Issue(BaseModel):\n", " incident_type: Literal[\"Service Outage\", \"Degradation\", \"Billing\", \"Security\", \"Data Loss\", \"Other\"] = Field(\n", " description=\"A high level type describing the incident.\"\n", " )\n", " description: str = Field(description=\"A detailed description of what is going wrong.\")\n", " affected_services: conlist(str, min_length=0) = Field(description=\"The services affected by the incident.\")\n", "\n", "\n", "class OutageArtifact(BaseModel):\n", " name: str = Field(description=\"How to address the customer.\")\n", " company: str = Field(description=\"The company the customer works for.\")\n", " role: str = Field(description=\"The role of the customer.\")\n", " email: str = Field(description=\"The best email to contact the customer.\", pattern=r\"^/^.+@.+$/$\")\n", " phone: str = Field(description=\"The best phone number to contact the customer.\", pattern=r\"^\\d{3}-\\d{3}-\\d{4}$\")\n", "\n", " incident_start: int = Field(\n", " description=\"About how many hours ago the incident started.\",\n", " )\n", " incident_end: int = Field(\n", " description=\"About how many hours ago the incident ended. If the incident is ongoing, set this to 0.\",\n", " )\n", "\n", " issues: conlist(Issue, min_length=1) = Field(description=\"The issues the customer is experiencing.\")\n", " additional_comments: conlist(str, min_length=0) = Field(\"Any additional comments the customer has.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's initialize the artifact as a standalone module.\n", "\n", "It requires a Kernel and LLM Service, alongside a Conversation object." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from semantic_kernel import Kernel\n", "from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n", "\n", "from guided_conversation.plugins.artifact import Artifact\n", "from guided_conversation.utils.conversation_helpers import Conversation\n", "\n", "kernel = Kernel()\n", "service_id = \"artifact_chat_completion\"\n", "chat_service = AzureChatCompletion(\n", " service_id=service_id,\n", " deployment_name=\"gpt-4o-2024-05-13\",\n", " api_version=\"2024-05-01-preview\",\n", ")\n", "kernel.add_service(chat_service)\n", "\n", "# Initialize the artifact\n", "artifact = Artifact(kernel, service_id, OutageArtifact, max_artifact_field_retries=2)\n", "conversation = Conversation()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To power the Artifact's ability to automatically fix issues, we provide the conversation history as additional context." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from semantic_kernel.contents import AuthorRole, ChatMessageContent\n", "\n", "conversation.add_messages(\n", " ChatMessageContent(\n", " role=AuthorRole.ASSISTANT,\n", " content=\"Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\",\n", " )\n", ")\n", "conversation.add_messages(\n", " ChatMessageContent(\n", " role=AuthorRole.USER,\n", " content=\"Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\",\n", " )\n", ")\n", "\n", "result = await artifact.update_artifact(\n", " field_name=\"name\",\n", " field_value=\"Jane Doe\",\n", " conversation=conversation,\n", ")\n", "conversation.add_messages(result.messages)\n", "\n", "result = await artifact.update_artifact(\n", " field_name=\"company\",\n", " field_value=\"Contoso\",\n", " conversation=conversation,\n", ")\n", "conversation.add_messages(result.messages)\n", "\n", "result = await artifact.update_artifact(\n", " field_name=\"role\",\n", " field_value=\"Database Administrator\",\n", " conversation=conversation,\n", ")\n", "conversation.add_messages(result.messages)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see how the artifact was updated with these valid updates and the resulting conversation messages that were generated.\n", "\n", "The Artifact creates messages whenever a field is updated for use in downstream agents like the main GuidedConversation." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Conversation up to this point:\n", "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n", "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n", "Assistant updated name to Jane Doe\n", "Assistant updated company to Contoso\n", "Assistant updated role to Database Administrator\n", "\n", "Current state of the artifact:\n", "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'email': 'Unanswered', 'phone': 'Unanswered', 'incident_start': 'Unanswered', 'incident_end': 'Unanswered', 'issues': 'Unanswered', 'additional_comments': 'Unanswered'}\n" ] } ], "source": [ "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\\n\")\n", "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we test an invalid update on a field with a regex. The agent should not update the artifact and\n", "instead resume the conversation because the provided email is incomplete." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Error updating field email: 1 validation error for Artifact\n", "email\n", " String should match pattern '^/^.+@.+$/$|Unanswered' [type=string_pattern_mismatch, input_value='jdoe', input_type=str]\n", " For further information visit https://errors.pydantic.dev/2.8/v/string_pattern_mismatch. Retrying...\n" ] } ], "source": [ "conversation.add_messages(\n", " ChatMessageContent(role=AuthorRole.ASSISTANT, content=\"What is the best email to contact you at?\")\n", ")\n", "conversation.add_messages(ChatMessageContent(role=AuthorRole.USER, content=\"my email is jdoe\"))\n", "result = await artifact.update_artifact(\n", " field_name=\"email\",\n", " field_value=\"jdoe\",\n", " conversation=conversation,\n", ")\n", "conversation.add_messages(result.messages)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the agent returned success, but did make an update (as shown by not generating a conversation message indicating such),\n", "then we implicitly assume the agent has resumed the conversation." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Conversation up to this point:\n", "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n", "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n", "Assistant updated name to Jane Doe\n", "Assistant updated company to Contoso\n", "Assistant updated role to Database Administrator\n", "Assistant: What is the best email to contact you at?\n", "None: my email is jdoe\n" ] } ], "source": [ "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's see what happens if we keep trying to update that failed field." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Error updating field email: 1 validation error for Artifact\n", "email\n", " String should match pattern '^/^.+@.+$/$|Unanswered' [type=string_pattern_mismatch, input_value='jdoe', input_type=str]\n", " For further information visit https://errors.pydantic.dev/2.8/v/string_pattern_mismatch. Retrying...\n", "Updating field email has failed too many times. Skipping.\n" ] } ], "source": [ "result = await artifact.update_artifact(\n", " field_name=\"email\",\n", " field_value=\"jdoe\",\n", " conversation=conversation,\n", ")\n", "\n", "# And again\n", "result = await artifact.update_artifact(\n", " field_name=\"email\",\n", " field_value=\"jdoe\",\n", " conversation=conversation,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we look at the current state of the artifact, we should see that the email has been removed\n", "since it has now failed 3 times which is greater than the max_artifact_field_retries parameter we set\n", "when we instantiated the artifact." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'name': 'Jane Doe',\n", " 'company': 'Contoso',\n", " 'role': 'Database Administrator',\n", " 'phone': 'Unanswered',\n", " 'incident_start': 'Unanswered',\n", " 'incident_end': 'Unanswered',\n", " 'issues': 'Unanswered',\n", " 'additional_comments': 'Unanswered'}" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "artifact.get_artifact_for_prompt()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's move on to trying to update a more complex field: the issues field." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Conversation up to this point:\n", "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n", "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n", "Assistant updated name to Jane Doe\n", "Assistant updated company to Contoso\n", "Assistant updated role to Database Administrator\n", "Assistant: What is the best email to contact you at?\n", "None: my email is jdoe\n", "Assistant: Can you tell me about the issues you're experiencing?\n", "None: The latency of accessing our database service has increased by 200\\% in the last 24 hours, \n", "even on a fresh instance. Additionally, we're seeing a lot of timeouts when trying to access the management portal.\n", "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\", 'affected_services': ['Database Service', 'Database Management Portal']}]\n", "\n", "Current state of the artifact:\n", "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'phone': 'Unanswered', 'incident_start': 'Unanswered', 'incident_end': 'Unanswered', 'issues': [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\", 'affected_services': ['Database Service', 'Database Management Portal']}], 'additional_comments': 'Unanswered'}\n" ] } ], "source": [ "conversation.add_messages(\n", " ChatMessageContent(role=AuthorRole.ASSISTANT, content=\"Can you tell me about the issues you're experiencing?\")\n", ")\n", "conversation.add_messages(\n", " ChatMessageContent(\n", " role=AuthorRole.USER,\n", " content=\"\"\"The latency of accessing our database service has increased by 200\\% in the last 24 hours, \n", "even on a fresh instance. Additionally, we're seeing a lot of timeouts when trying to access the management portal.\"\"\",\n", " )\n", ")\n", "\n", "result = await artifact.update_artifact(\n", " field_name=\"issues\",\n", " field_value=[\n", " {\n", " \"incident_type\": \"Degradation\",\n", " \"description\": \"\"\"The latency of accessing the customer's database service has increased by 200% in the \\\n", "last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\"\"\",\n", " \"affected_services\": [\"Database Service\", \"Database Management Portal\"],\n", " }\n", " ],\n", " conversation=conversation,\n", ")\n", "conversation.add_messages(result.messages)\n", "\n", "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\\n\")\n", "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To add another affected service, we can need to update the issues field with the new value again.\n", "The obvious con of this approach is that the model generating the field_value has to regenerate the entire field_value.\n", "However, the pro is that keeps the available tools simple for the model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Conversation up to this point:\n", "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n", "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n", "Assistant updated name to Jane Doe\n", "Assistant updated company to Contoso\n", "Assistant updated role to Database Administrator\n", "Assistant: What is the best email to contact you at?\n", "None: my email is jdoe\n", "Assistant: Can you tell me about the issues you're experiencing?\n", "None: The latency of accessing our database service has increased by 200\\% in the last 24 hours, \n", "even on a fresh instance. Additionally, we're seeing a lot of timeouts when trying to access the management portal.\n", "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\", 'affected_services': ['Database Service', 'Database Management Portal']}]\n", "Assistant: Is there anything else you'd like to add about the issues you're experiencing?\n", "None: Yes another thing that is effected is access to billing information is very slow.\n", "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}]\n", "\n", "Current state of the artifact:\n", "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'phone': 'Unanswered', 'incident_start': 'Unanswered', 'incident_end': 'Unanswered', 'issues': [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}], 'additional_comments': 'Unanswered'}\n" ] } ], "source": [ "conversation.add_messages(\n", " ChatMessageContent(\n", " role=AuthorRole.ASSISTANT,\n", " content=\"Is there anything else you'd like to add about the issues you're experiencing?\",\n", " )\n", ")\n", "conversation.add_messages(\n", " ChatMessageContent(\n", " role=AuthorRole.USER,\n", " content=\"Yes another thing that is effected is access to billing information is very slow.\",\n", " )\n", ")\n", "\n", "result = await artifact.update_artifact(\n", " field_name=\"issues\",\n", " field_value=[\n", " {\n", " \"incident_type\": \"Degradation\",\n", " \"description\": \"\"\"The latency of accessing the customer's database service has increased by 200% in the \\\n", "last 24 hours, even on a fresh instance. They also report timeouts when trying to access the \\\n", "management portal and slowdowns in the access to billing information.\"\"\",\n", " \"affected_services\": [\"Database Service\", \"Database Management Portal\", \"Billing portal\"],\n", " },\n", " ],\n", " conversation=conversation,\n", ")\n", "conversation.add_messages(result.messages)\n", "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\\n\")\n", "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's see what happens if we try to update a field that is not in the artifact." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Was the update successful? False\n", "Conversation up to this point:\n", "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n", "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n", "Assistant updated name to Jane Doe\n", "Assistant updated company to Contoso\n", "Assistant updated role to Database Administrator\n", "Assistant: What is the best email to contact you at?\n", "None: my email is jdoe\n", "Assistant: Can you tell me about the issues you're experiencing?\n", "None: The latency of accessing our database service has increased by 200\\% in the last 24 hours, \n", "even on a fresh instance. Additionally, we're seeing a lot of timeouts when trying to access the management portal.\n", "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\", 'affected_services': ['Database Service', 'Database Management Portal']}]\n", "Assistant: Is there anything else you'd like to add about the issues you're experiencing?\n", "None: Yes another thing that is effected is access to billing information is very slow.\n", "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}]\n", "\n", "Current state of the artifact:\n", "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'phone': 'Unanswered', 'incident_start': 'Unanswered', 'incident_end': 'Unanswered', 'issues': [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}], 'additional_comments': 'Unanswered'}\n" ] } ], "source": [ "result = await artifact.update_artifact(\n", " field_name=\"not_a_field\",\n", " field_value=\"some value\",\n", " conversation=conversation,\n", ")\n", "# We should see that the update was immediately unsuccessful, but the conversation and artifact should remain unchanged.\n", "print(f\"Was the update successful? {result.update_successful}\")\n", "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\\n\")\n", "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, let's see what happens if we try to update a field with the incorrect type, but the correct information was provided in the conversation. \n", "We should see the agent correctly updated the field correctly as an integer." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Error updating field incident_start: 2 validation errors for Artifact\n", "incident_start.int\n", " Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='3 hours', input_type=str]\n", " For further information visit https://errors.pydantic.dev/2.8/v/int_parsing\n", "incident_start.literal['Unanswered']\n", " Input should be 'Unanswered' [type=literal_error, input_value='3 hours', input_type=str]\n", " For further information visit https://errors.pydantic.dev/2.8/v/literal_error. Retrying...\n", "Agent failed to fix field incident_start. Retrying...\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Current state of the artifact:\n", "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'phone': 'Unanswered', 'incident_start': 3, 'incident_end': 'Unanswered', 'issues': [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}], 'additional_comments': 'Unanswered'}\n" ] } ], "source": [ "conversation.add_messages(\n", " ChatMessageContent(role=AuthorRole.ASSISTANT, content=\"How many hours ago did the incident start?\")\n", ")\n", "conversation.add_messages(ChatMessageContent(role=AuthorRole.USER, content=\"about 3 hours ago\"))\n", "result = await artifact.update_artifact(\n", " field_name=\"incident_start\",\n", " field_value=\"3 hours\",\n", " conversation=conversation,\n", ")\n", "\n", "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" } }, "nbformat": 4, "nbformat_minor": 2 }