From f973c3e4dd7a79dda6841f76d975dfc05c5f79ef Mon Sep 17 00:00:00 2001 From: Ben Constable Date: Thu, 12 Sep 2024 05:33:53 +0100 Subject: [PATCH] Add Vector Based Text2SQL Code and Approach (#14) * Add deployment for text2sql index * Add entities for new version * Work in progress * Add deployment for text2sql index * Add entities for new version * Work in progress * refactor the location * Update envs * Update the envs * Update envs * Finish indexer building * Update the location of the dictionary * Update data dictionary * Update the plugin to load jsonl * Update content * Move to use a skillset for indexing * Update the scripts * Improve the readme * Update the naming * Fix bad replacement * Update the readmes * Update readme * Update the readme * Run the vector example * Update the env * Update the code * Update readme * Update main readme --- README.md | 10 +- adi_function_app/.env | 12 + adi_function_app/README.md | 4 +- adi_function_app/adi_2_ai_search.py | 10 +- adi_function_app/key_phrase_extraction.py | 4 +- deploy_ai_search/.env | 22 + deploy_ai_search/README.md | 16 +- deploy_ai_search/ai_search.py | 29 +- deploy_ai_search/deploy.py | 5 + deploy_ai_search/environment.py | 1 + deploy_ai_search/rag_documents.py | 1 + deploy_ai_search/text_2_sql.py | 210 ++++ text2sql/.env | 10 - .../OneShot SQL vs TwoShot SQL OpenAI.png | Bin 39286 -> 0 bytes text2sql/plugins/sql_plugin/entities.json | 394 ------- .../plugins/sql_plugin/entities_schema.json | 155 --- text_2_sql/.env | 16 + {text2sql => text_2_sql}/README.md | 112 +- text_2_sql/data_dictionary/entities.json | 384 ++++++ .../OneShot SQL vs TwoShot SQL OpenAI.png | Bin 0 -> 223764 bytes .../plugins/ai_search_plugin/__init__.py | 0 .../ai_search_plugin/ai_search_plugin.py | 194 +-- .../prompt_based_sql_plugin}/__init__.py | 0 .../prompt_based_sql_plugin.py | 305 +++-- .../vector_based_sql_plugin/__init__.py | 0 .../vector_based_sql_plugin.py | 190 +++ {text2sql => text_2_sql}/prompt.yaml | 224 ++-- .../rag_with_ai_search_and_text_2_sql.ipynb | 10 +- .../rag_with_prompt_based_text_2_sql.ipynb | 10 +- .../rag_with_vector_based_text_2_sql.ipynb | 1042 +++++++++++++++++ {text2sql => text_2_sql}/requirements.txt | 12 +- 31 files changed, 2395 insertions(+), 987 deletions(-) create mode 100644 adi_function_app/.env create mode 100644 deploy_ai_search/.env create mode 100644 deploy_ai_search/text_2_sql.py delete mode 100644 text2sql/.env delete mode 100644 text2sql/images/OneShot SQL vs TwoShot SQL OpenAI.png delete mode 100644 text2sql/plugins/sql_plugin/entities.json delete mode 100644 text2sql/plugins/sql_plugin/entities_schema.json create mode 100644 text_2_sql/.env rename {text2sql => text_2_sql}/README.md (66%) create mode 100644 text_2_sql/data_dictionary/entities.json create mode 100644 text_2_sql/images/OneShot SQL vs TwoShot SQL OpenAI.png rename {text2sql => text_2_sql}/plugins/ai_search_plugin/__init__.py (100%) rename {text2sql => text_2_sql}/plugins/ai_search_plugin/ai_search_plugin.py (59%) rename {text2sql/plugins/sql_plugin => text_2_sql/plugins/prompt_based_sql_plugin}/__init__.py (100%) rename text2sql/plugins/sql_plugin/sql_plugin.py => text_2_sql/plugins/prompt_based_sql_plugin/prompt_based_sql_plugin.py (76%) create mode 100644 text_2_sql/plugins/vector_based_sql_plugin/__init__.py create mode 100644 text_2_sql/plugins/vector_based_sql_plugin/vector_based_sql_plugin.py rename {text2sql => text_2_sql}/prompt.yaml (97%) rename {text2sql => text_2_sql}/rag_with_ai_search_and_text_2_sql.ipynb (97%) rename text2sql/rag_with_text_2_sql.ipynb => text_2_sql/rag_with_prompt_based_text_2_sql.ipynb (99%) create mode 100644 text_2_sql/rag_with_vector_based_text_2_sql.ipynb rename {text2sql => text_2_sql}/requirements.txt (94%) diff --git a/README.md b/README.md index 2b649a2..161277c 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,13 @@ It is intended that the plugins and skills provided in this repository, are adap ## Components -- `./text2sql` contains an Multi-Shot implementation for Text2SQL generation and querying which can be used to answer questions backed by a database as a knowledge base. -- `./ai_search_with_adi_function_app` contains code for linking Azure Document Intelligence with AI Search to process complex documents with charts and images, and uses multi-modal models (gpt4o) to interpret and understand these. -- `./deploy_ai_search` provides an easy Python based utility for deploying an index, indexer and corresponding skillset for AI Search. +- `./text_2_sql` contains an two Multi-Shot implementation for Text2SQL generation and querying which can be used to answer questions backed by a database as a knowledge base. A prompt based and vector based approach are shown, both of which exhibit great performance in answering sql queries. With these plugins, your RAG application can now access and pull data from any SQL table exposed to it to answer questions. +- `./adi_function_app` contains code for linking Azure Document Intelligence with AI Search to process complex documents with charts and images, and uses multi-modal models (gpt4o) to interpret and understand these. With this custom skill, the RAG application can draw insights from complex charts and images during the vector search. +- `./deploy_ai_search` provides an easy Python based utility for deploying an index, indexer and corresponding skillset for AI Search and for Text2SQL. -The above components have been successfully used on production RAG projects to increase the quality of responses. The code provided in this repo is a sample of the implementation and should be adjusted before being used in production. +The above components have been successfully used on production RAG projects to increase the quality of responses. + +_The code provided in this repo is a sample of the implementation and should be adjusted before being used in production._ ## High Level Implementation diff --git a/adi_function_app/.env b/adi_function_app/.env new file mode 100644 index 0000000..eb0ec41 --- /dev/null +++ b/adi_function_app/.env @@ -0,0 +1,12 @@ +FunctionApp__ClientId= +IdentityType= # system_assigned or user_assigned or key +OpenAI__ApiKey= +OpenAI__Endpoint= +OpenAI__MultiModalDeployment= +OpenAI__ApiVersion= +AIService__DocumentIntelligence__Endpoint= +AIService__DocumentIntelligence__Key= +AIService__Language__Endpoint= +AIService__Language__Key= +StorageAccount__Endpoint= +StorageAccount__ConnectionString= diff --git a/adi_function_app/README.md b/adi_function_app/README.md index 3638b88..72c60ab 100644 --- a/adi_function_app/README.md +++ b/adi_function_app/README.md @@ -43,11 +43,11 @@ The properties returned from the ADI Custom Skill are then used to perform the f ## Deploying AI Search Setup -To deploy the pre-built index and associated indexer / skillset setup, see instructions in `./ai_search/README.md`. +To deploy the pre-built index and associated indexer / skillset setup, see instructions in `./deploy_ai_search/README.md`. ## ADI Custom Skill -Deploy the associated function app and required resources. You can then experiment with the custom skill by sending an HTTP request in the AI Search JSON format to the `/adi_2_deploy_ai_search` HTTP endpoint. +Deploy the associated function app and required resources. You can then experiment with the custom skill by sending an HTTP request in the AI Search JSON format to the `/adi_2_ai_search` HTTP endpoint. To use with an index, either use the utility to configure a indexer in the provided form, or integrate the skill with your skillset pipeline. diff --git a/adi_function_app/adi_2_ai_search.py b/adi_function_app/adi_2_ai_search.py index 8597550..2f434f4 100644 --- a/adi_function_app/adi_2_ai_search.py +++ b/adi_function_app/adi_2_ai_search.py @@ -188,11 +188,11 @@ async def understand_image_with_gptv(image_base64, caption, tries_left=3): "role": "user", "content": [ { - "type": "text", + "Type": "text", "text": user_input, }, { - "type": "image_url", + "Type": "image_url", "image_url": { "url": f"data:image/png;base64,{image_base64}" }, @@ -371,10 +371,12 @@ async def analyse_document(file_path: str) -> AnalyzeResult: managed_identity_client_id=os.environ["FunctionApp__ClientId"] ) else: - credential = AzureKeyCredential(os.environ["AIService__Services__Key"]) + credential = AzureKeyCredential( + os.environ["AIService__DocumentIntelligence__Key"] + ) async with DocumentIntelligenceClient( - endpoint=os.environ["AIService__Services__Endpoint"], + endpoint=os.environ["AIService__DocumentIntelligence__Endpoint"], credential=credential, ) as document_intelligence_client: poller = await document_intelligence_client.begin_analyze_document( diff --git a/adi_function_app/key_phrase_extraction.py b/adi_function_app/key_phrase_extraction.py index c93d62a..01bb8aa 100644 --- a/adi_function_app/key_phrase_extraction.py +++ b/adi_function_app/key_phrase_extraction.py @@ -45,9 +45,9 @@ async def extract_key_phrases_from_text( managed_identity_client_id=os.environ.get("FunctionApp__ClientId") ) else: - credential = AzureKeyCredential(os.environ.get("AIService__Services__Key")) + credential = AzureKeyCredential(os.environ.get("AIService__Language__Key")) text_analytics_client = TextAnalyticsClient( - endpoint=os.environ.get("AIService__Services__Endpoint"), + endpoint=os.environ.get("AIService__Language__Endpoint"), credential=credential, ) diff --git a/deploy_ai_search/.env b/deploy_ai_search/.env new file mode 100644 index 0000000..68897be --- /dev/null +++ b/deploy_ai_search/.env @@ -0,0 +1,22 @@ +FunctionApp__Endpoint= +FunctionApp__Key= +FunctionApp__PreEmbeddingCleaner__FunctionName=pre_embedding_cleaner +FunctionApp__ADI__FunctionName=adi_2_ai_search +FunctionApp__KeyPhraseExtractor__FunctionName=key_phrase_extractor +FunctionApp__AppRegistrationResourceId= +IdentityType= # system_assigned or user_assigned or key +AIService__AzureSearchOptions__Endpoint= +AIService__AzureSearchOptions__Identity__ClientId= +AIService__AzureSearchOptions__Key= +AIService__AzureSearchOptions__UsePrivateEndpoint= +AIService__AzureSearchOptions__Identity__FQName= +StorageAccount__FQEndpoint= +StorageAccount__ConnectionString= +StorageAccount__RagDocuments__Container= +StorageAccount__Text2Sql__Container= +OpenAI__ApiKey= +OpenAI__Endpoint= +OpenAI__EmbeddingModel= +OpenAI__EmbeddingDeployment= +OpenAI__EmbeddingDimensions=1536 +Text2Sql__DatabaseName= diff --git a/deploy_ai_search/README.md b/deploy_ai_search/README.md index c124cd5..0e30f1e 100644 --- a/deploy_ai_search/README.md +++ b/deploy_ai_search/README.md @@ -1,18 +1,28 @@ -# AI Search Indexing with Azure Document Intelligence - Pre-built Index Setup +# AI Search Indexing Pre-built Index Setup The associated scripts in this portion of the repository contains pre-built scripts to deploy the skillset with Azure Document Intelligence. -## Steps +## Steps for Rag Documents Index Deployment 1. Update `.env` file with the associated values. Not all values are required dependent on whether you are using System / User Assigned Identities or a Key based authentication. 2. Adjust `rag_documents.py` with any changes to the index / indexer. The `get_skills()` method implements the skills pipeline. Make any adjustments here in the skills needed to enrich the data source. 3. Run `deploy.py` with the following args: - - `indexer_type rag`. This selects the `rag_documents` sub class. + - `indexer_type rag`. This selects the `RagDocumentsAISearch` sub class. - `enable_page_chunking True`. This determines whether page wise chunking is applied in ADI, or whether the inbuilt skill is used for TextSplit. **Page wise analysis in ADI is recommended to avoid splitting tables / figures across multiple chunks, when the chunking is performed.** - `rebuild`. Whether to delete and rebuild the index. - `suffix`. Optional parameter that will apply a suffix onto the deployed index and indexer. This is useful if you want deploy a test version, before overwriting the main version. +## Steps for Text2SQL Index Deployment + +1. Update `.env` file with the associated values. Not all values are required dependent on whether you are using System / User Assigned Identities or a Key based authentication. +2. Adjust `text_2_sql.py` with any changes to the index / indexer. The `get_skills()` method implements the skills pipeline. Make any adjustments here in the skills needed to enrich the data source. +3. Run `deploy.py` with the following args: + + - `indexer_type text_2_sql`. This selects the `Text2SQLAISearch` sub class. + - `rebuild`. Whether to delete and rebuild the index. + - `suffix`. Optional parameter that will apply a suffix onto the deployed index and indexer. This is useful if you want deploy a test version, before overwriting the main version. + ## ai_search.py & environment.py This includes a variety of helper files and scripts to deploy the index setup. This is useful for CI/CD to avoid having to write JSON files manually or use the UI to deploy the pipeline. diff --git a/deploy_ai_search/ai_search.py b/deploy_ai_search/ai_search.py index 75a6bf0..2d9a650 100644 --- a/deploy_ai_search/ai_search.py +++ b/deploy_ai_search/ai_search.py @@ -25,6 +25,7 @@ SynonymMap, SplitSkill, SearchIndexerIndexProjections, + BlobIndexerParsingMode, ) from azure.core.exceptions import HttpResponseError from azure.search.documents.indexes import SearchIndexerClient, SearchIndexClient @@ -66,12 +67,16 @@ def __init__( self.environment = AISearchEnvironment(indexer_type=self.indexer_type) self._search_indexer_client = SearchIndexerClient( - self.environment.ai_search_endpoint, self.environment.ai_search_credential + endpoint=self.environment.ai_search_endpoint, + credential=self.environment.ai_search_credential, ) self._search_index_client = SearchIndexClient( - self.environment.ai_search_endpoint, self.environment.ai_search_credential + endpoint=self.environment.ai_search_endpoint, + credential=self.environment.ai_search_credential, ) + self.parsing_mode = BlobIndexerParsingMode.DEFAULT + @property def indexer_name(self): """Get the indexer name for the indexer.""" @@ -156,7 +161,16 @@ def get_data_source(self) -> SearchIndexerDataSourceConnection: if self.get_indexer() is None: return None - data_deletion_detection_policy = NativeBlobSoftDeleteDeletionDetectionPolicy() + if self.parsing_mode in [ + BlobIndexerParsingMode.DEFAULT, + BlobIndexerParsingMode.TEXT, + BlobIndexerParsingMode.JSON, + ]: + data_deletion_detection_policy = ( + NativeBlobSoftDeleteDeletionDetectionPolicy() + ) + else: + data_deletion_detection_policy = None data_change_detection_policy = HighWaterMarkChangeDetectionPolicy( high_water_mark_column_name="metadata_storage_last_modified" @@ -268,6 +282,10 @@ def get_text_split_skill(self, context, source) -> SplitSkill: def get_adi_skill(self, chunk_by_page=False) -> WebApiSkill: """Get the custom skill for adi. + Args: + ----- + chunk_by_page (bool, optional): Whether to chunk by page. Defaults to False. + Returns: -------- WebApiSkill: The custom skill for adi""" @@ -528,6 +546,11 @@ def run_indexer(self): def reset_indexer(self): """This function runs the indexer.""" + + if self.get_indexer() is None: + logging.warning("Indexer not defined. Skipping reset operation.") + + return self._search_indexer_client.reset_indexer(self.indexer_name) logging.info("%s reset.", self.indexer_name) diff --git a/deploy_ai_search/deploy.py b/deploy_ai_search/deploy.py index 7254c03..ee40aef 100644 --- a/deploy_ai_search/deploy.py +++ b/deploy_ai_search/deploy.py @@ -2,6 +2,7 @@ # Licensed under the MIT License. import argparse from rag_documents import RagDocumentsAISearch +from text_2_sql import Text2SqlAISearch def deploy_config(arguments: argparse.Namespace): @@ -15,6 +16,10 @@ def deploy_config(arguments: argparse.Namespace): rebuild=arguments.rebuild, enable_page_by_chunking=arguments.enable_page_chunking, ) + elif arguments.indexer_type == "text_2_sql": + index_config = Text2SqlAISearch( + suffix=arguments.suffix, rebuild=arguments.rebuild + ) else: raise ValueError("Invalid Indexer Type") diff --git a/deploy_ai_search/environment.py b/deploy_ai_search/environment.py index d7cbdb4..5b67c08 100644 --- a/deploy_ai_search/environment.py +++ b/deploy_ai_search/environment.py @@ -12,6 +12,7 @@ class IndexerType(Enum): """The type of the indexer""" RAG_DOCUMENTS = "rag-documents" + TEXT_2_SQL = "text-2-sql" class IdentityType(Enum): diff --git a/deploy_ai_search/rag_documents.py b/deploy_ai_search/rag_documents.py index 5afa932..1fb03ac 100644 --- a/deploy_ai_search/rag_documents.py +++ b/deploy_ai_search/rag_documents.py @@ -249,6 +249,7 @@ def get_indexer(self) -> SearchIndexer: fail_on_unsupported_content_type=False, index_storage_metadata_only_for_oversized_documents=True, indexed_file_name_extensions=".pdf,.pptx,.docx,.xlsx,.txt,.png,.jpg,.jpeg", + parsing_mode=self.parsing_mode, ), max_failed_items=5, ) diff --git a/deploy_ai_search/text_2_sql.py b/deploy_ai_search/text_2_sql.py new file mode 100644 index 0000000..6f44c0f --- /dev/null +++ b/deploy_ai_search/text_2_sql.py @@ -0,0 +1,210 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +from azure.search.documents.indexes.models import ( + SearchFieldDataType, + SearchField, + SearchableField, + SemanticField, + SemanticPrioritizedFields, + SemanticConfiguration, + SemanticSearch, + SearchIndexer, + FieldMapping, + SimpleField, + ComplexField, + IndexingParameters, + IndexingParametersConfiguration, + BlobIndexerDataToExtract, + IndexerExecutionEnvironment, + BlobIndexerParsingMode, +) +from ai_search import AISearch +from environment import ( + IndexerType, +) + + +class Text2SqlAISearch(AISearch): + """This class is used to deploy the sql index.""" + + def __init__(self, suffix: str | None = None, rebuild: bool | None = False): + """Initialize the Text2SqlAISearch class. This class implements the deployment of the sql index. + + Args: + suffix (str, optional): The suffix for the indexer. Defaults to None. If an suffix is provided, it is assumed to be a test indexer. + rebuild (bool, optional): Whether to rebuild the index. Defaults to False. + """ + self.indexer_type = IndexerType.TEXT_2_SQL + super().__init__(suffix, rebuild) + + self.parsing_mode = BlobIndexerParsingMode.JSON_ARRAY + + self.entities = [] + + def get_index_fields(self) -> list[SearchableField]: + """This function returns the index fields for sql index. + + Returns: + list[SearchableField]: The index fields for sql index""" + + fields = [ + SearchableField( + name="Entity", + type=SearchFieldDataType.String, + key=True, + analyzer_name="keyword", + ), + SearchableField( + name="EntityName", type=SearchFieldDataType.String, filterable=True + ), + SearchableField( + name="Description", + type=SearchFieldDataType.String, + sortable=False, + filterable=False, + facetable=False, + ), + SearchField( + name="DescriptionEmbedding", + type=SearchFieldDataType.Collection(SearchFieldDataType.Single), + vector_search_dimensions=self.environment.open_ai_embedding_dimensions, + vector_search_profile_name=self.vector_search_profile_name, + ), + ComplexField( + name="Columns", + collection=True, + fields=[ + SearchableField(name="Name", type=SearchFieldDataType.String), + SearchableField(name="Definition", type=SearchFieldDataType.String), + SearchableField(name="Type", type=SearchFieldDataType.String), + SimpleField( + name="AllowedValues", + type=SearchFieldDataType.String, + collection=True, + ), + SimpleField( + name="SampleValues", + type=SearchFieldDataType.String, + collection=True, + ), + ], + ), + SearchableField( + name="ColumnNames", + type=SearchFieldDataType.String, + collection=True, + hidden=True, + ), # This is needed to enable semantic searching against the column names as complex field types are not used. + ] + + return fields + + def get_semantic_search(self) -> SemanticSearch: + """This function returns the semantic search configuration for sql index + + Returns: + SemanticSearch: The semantic search configuration""" + + semantic_config = SemanticConfiguration( + name=self.semantic_config_name, + prioritized_fields=SemanticPrioritizedFields( + title_field=SemanticField(field_name="EntityName"), + content_fields=[ + SemanticField(field_name="Description"), + ], + keywords_fields=[ + SemanticField(field_name="ColumnNames"), + SemanticField(field_name="Entity"), + ], + ), + ) + + semantic_search = SemanticSearch(configurations=[semantic_config]) + + return semantic_search + + def get_skills(self) -> list: + """Get the skillset for the indexer. + + Returns: + list: The skillsets used in the indexer""" + + embedding_skill = self.get_vector_skill( + "/document", "/document/Description", target_name="DescriptionEmbedding" + ) + + skills = [embedding_skill] + + return skills + + def get_indexer(self) -> SearchIndexer: + """This function returns the indexer for sql. + + Returns: + SearchIndexer: The indexer for sql""" + + # Only place on schedule if it is not a test deployment + if self.test: + schedule = None + batch_size = 4 + else: + schedule = {"interval": "PT24H"} + batch_size = 16 + + if self.environment.use_private_endpoint: + execution_environment = IndexerExecutionEnvironment.PRIVATE + else: + execution_environment = IndexerExecutionEnvironment.STANDARD + + indexer_parameters = IndexingParameters( + batch_size=batch_size, + configuration=IndexingParametersConfiguration( + data_to_extract=BlobIndexerDataToExtract.CONTENT_AND_METADATA, + query_timeout=None, + execution_environment=execution_environment, + fail_on_unprocessable_document=False, + fail_on_unsupported_content_type=False, + index_storage_metadata_only_for_oversized_documents=True, + indexed_file_name_extensions=".json", + parsing_mode=self.parsing_mode, + ), + max_failed_items=5, + ) + + indexer = SearchIndexer( + name=self.indexer_name, + description="Indexer to sql entities and generate embeddings", + skillset_name=self.skillset_name, + target_index_name=self.index_name, + data_source_name=self.data_source_name, + schedule=schedule, + output_field_mappings=[ + FieldMapping( + source_field_name="/document/Entity", target_field_name="Entity" + ), + FieldMapping( + source_field_name="/document/EntityName", + target_field_name="EntityName", + ), + FieldMapping( + source_field_name="/document/Description", + target_field_name="Description", + ), + FieldMapping( + source_field_name="/document/DescriptionEmbedding", + target_field_name="DescriptionEmbedding", + ), + FieldMapping( + source_field_name="/document/Columns", + target_field_name="Columns", + ), + FieldMapping( + source_field_name="/document/Columns/*/Name", + target_field_name="ColumnNames", + ), + ], + parameters=indexer_parameters, + ) + + return indexer diff --git a/text2sql/.env b/text2sql/.env deleted file mode 100644 index 6b28e2c..0000000 --- a/text2sql/.env +++ /dev/null @@ -1,10 +0,0 @@ -OPEN_AI_CONVERSATION_MODEL= -OPEN_AI_EMBEDDING_MODEL= -OPEN_AI_ENDPOINT= -OPEN_AI_KEY= -SQL_DB_ENGINE= -SQL_DB_NAME= -SQL_DB_CONNECTION_STRING= -AI_SEARCH_ENDPOINT= -AI_SEARCH_INDEX= -AI_SEARCH_SEMANTIC_CONFIG= diff --git a/text2sql/images/OneShot SQL vs TwoShot SQL OpenAI.png b/text2sql/images/OneShot SQL vs TwoShot SQL OpenAI.png deleted file mode 100644 index e89a5bbf1504e163545389a4dd45b283f2075b91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39286 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 MUHx3vIVCg!00yMcZU6uP diff --git a/text2sql/plugins/sql_plugin/entities.json b/text2sql/plugins/sql_plugin/entities.json deleted file mode 100644 index 4638f41..0000000 --- a/text2sql/plugins/sql_plugin/entities.json +++ /dev/null @@ -1,394 +0,0 @@ -{ - "tables": [ - { - "columns": [ - { - "definition": "A unique identifier for each sales order ticket. This ID is auto-generated and serves as the primary key for the SalesOrderTicket table.", - "name": "SalesOrderID", - "type": "INT" - }, - { - "definition": "The date and time when the sales order was created. This is used to track when the order was initiated.", - "name": "OrderDate", - "type": "DATETIME" - }, - { - "definition": "The date by which the order is expected to be fulfilled or delivered. It helps in managing delivery timelines.", - "name": "DueDate", - "type": "DATETIME" - }, - { - "definition": "The date when the order was shipped to the customer. This is used for tracking shipping and fulfillment status.", - "name": "ShipDate", - "type": "DATETIME" - }, - { - "allowed_values": [ - 1, - 2, - 3 - ], - "definition": "The current status of the order, represented as a numeric code (e.g., 1 for In Progress, 2 for Completed, 3 for Canceled).", - "name": "Status", - "type": "TINYINT" - }, - { - "definition": "The total amount due for the order, including all line items, taxes, and shipping charges.", - "name": "TotalDue", - "type": "MONEY" - }, - { - "definition": "The date and time when the sales order ticket record was last modified. This is used for tracking updates and changes to the order.", - "name": "ModifiedDate", - "type": "DATETIME" - } - ], - "description": "This table stores detailed information about sales order tickets, including the order details, customer information, order status, and timestamps. It is used to manage and track sales orders throughout the order lifecycle, from creation to fulfillment.", - "entity": "SalesOrderDetail", - "selector": "Use this table to retrieve or store information related to individual sales order tickets. This is applicable when processing sales orders, managing customer transactions, or tracking order fulfillment status.", - "table_name": "Sales Order Detail" - }, - { - "columns": [ - { - "definition": "A unique identifier for each sales order. This ID is auto-generated and serves as the primary key for the SalesOrderHeader table.", - "name": "SalesOrderID", - "type": "INT" - }, - { - "definition": "The date and time when the sales order was created. This field is used to track when the order was initiated.", - "name": "OrderDate", - "type": "DATETIME" - }, - { - "definition": "The date by which the order is expected to be fulfilled or delivered. It helps in managing delivery timelines.", - "name": "DueDate", - "type": "DATETIME" - }, - { - "definition": "The date when the order was shipped to the customer. This is used for tracking shipping and fulfillment status.", - "name": "ShipDate", - "type": "DATETIME" - }, - { - "allowed_values": [ - 1, - 2, - 3 - ], - "definition": "The current status of the order, represented as a numeric code (e.g., 1 for In Progress, 2 for Completed, 3 for Canceled).", - "name": "Status", - "type": "TINYINT" - }, - { - "allowed_values": [ - "True", - "False" - ], - "definition": "Indicates whether the order was placed online.", - "name": "OnlineOrderFlag", - "type": "BIT" - }, - { - "definition": "A unique order number assigned to the sales order. This is used for tracking and identification purposes.", - "name": "SalesOrderNumber", - "type": "NVARCHAR(25)" - }, - { - "definition": "The purchase order number provided by the customer. This field links the sales order to the customer's purchase order.", - "name": "PurchaseOrderNumber", - "type": "NVARCHAR(25)" - }, - { - "definition": "The account number of the customer placing the order. This helps link the order to the customer's account.", - "name": "AccountNumber", - "type": "NVARCHAR(15)" - }, - { - "definition": "A foreign key that links to the Customer table, representing the customer who placed the order.", - "name": "CustomerID", - "type": "INT" - }, - { - "definition": "A foreign key that links to the Address table, representing the shipping address for the order.", - "name": "ShipToAddressID", - "type": "INT" - }, - { - "definition": "A foreign key that links to the Address table, representing the billing address for the order.", - "name": "BillToAddressID", - "type": "INT" - }, - { - "definition": "The shipping method used for the order (e.g., UPS, FedEx). This field helps track shipping preferences.", - "name": "ShipMethod", - "type": "NVARCHAR(50)" - }, - { - "definition": "The total cost of the order before taxes and shipping charges. This field is used to calculate the final total. The currency is pound sterling (GBP).", - "name": "SubTotal", - "type": "MONEY" - }, - { - "definition": "The tax amount applied to the order. This is calculated based on the order subtotal and applicable tax rates. The currency is pound sterling (GBP).", - "name": "TaxAmt", - "type": "MONEY" - }, - { - "definition": "The shipping charge applied to the order. This field represents the cost of shipping the order to the customer. The currency is pound sterling (GBP).", - "name": "Freight", - "type": "MONEY" - }, - { - "definition": "The total amount due for the order, including all line items, taxes, and shipping charges. The currency is pound sterling (GBP).", - "name": "TotalDue", - "type": "MONEY" - }, - { - "definition": "Any additional comments or notes related to the sales order. This field can include special instructions or remarks.", - "name": "Comment", - "type": "NVARCHAR(255)" - }, - { - "definition": "The date and time when the sales order header record was last modified. This is used for tracking updates and changes to the order.", - "name": "ModifiedDate", - "type": "DATETIME" - } - ], - "description": "This table contains high-level information about sales orders, including order dates, customer details, shipping information, and order status. It is used to manage and track sales orders from initiation to fulfillment.", - "entity": "SalesOrderHeader", - "selector": "Use this table to retrieve or store information related to the overall details of a sales order. It is applicable when you need to track order status, manage order dates, or relate orders to customers and shipping information.", - "table_name": "Sales Order Header" - }, - { - "columns": [ - { - "definition": "A unique identifier for each address. This ID is auto-generated and serves as the primary key for the Address table.", - "name": "AddressID", - "type": "INT" - }, - { - "definition": "The city in which the address is located. This is used to specify the city for the address.", - "name": "City", - "type": "NVARCHAR(30)" - }, - { - "definition": "The state or province in which the address is located. This is used to specify the state or province for the address.", - "name": "StateProvince", - "type": "NVARCHAR(50)" - }, - { - "definition": "The country or region in which the address is located. This is used to specify the country or region for the address.", - "name": "CountryRegion", - "type": "NVARCHAR(50)" - }, - { - "definition": "The postal code associated with the address. This is used to specify the postal code for the address, which helps in geographical sorting and shipping.", - "name": "PostalCode", - "type": "NVARCHAR(15)" - }, - { - "definition": "The date and time when the address record was last modified. This is used for tracking updates and changes to the address information.", - "name": "ModifiedDate", - "type": "DATETIME" - } - ], - "description": "This table stores address information for customers, including street addresses, city, state, postal code, and country/region. It is used to maintain contact and shipping information for orders, as well as to manage customer locations.", - "entity": "Address", - "selector": "Use this table to retrieve or store address details for customers, shipping locations, or billing addresses. It is applicable in scenarios where location information is required, such as shipping orders, verifying customer addresses, or managing geographical data.", - "table_name": "Address" - } - ], - "views": [ - { - "columns": [ - { - "definition": "A unique identifier for each product category. This ID is used to reference specific categories.", - "name": "ProductCategoryID", - "type": "INT" - }, - { - "definition": "The name of the parent product category. This represents the top-level category under which subcategories are grouped.", - "name": "ParentProductCategoryName", - "type": "NVARCHAR(50)" - }, - { - "definition": "The name of the product category. This can refer to either a top-level category or a subcategory, depending on the context.", - "name": "ProductCategoryName", - "type": "NVARCHAR(50)" - } - ], - "description": "This view provides a comprehensive list of all product categories and their corresponding subcategories in the SalesLT schema of the AdventureWorksLT database. It is used to understand the hierarchical structure of product categories, facilitating product organization and categorization.", - "entity": "vGetAllCategories", - "selector": "Use this view to retrieve information about product categories and subcategories. It is useful for scenarios where product categorization is required, such as generating reports based on product categories or filtering products by category.", - "view_name": "Get All Categories" - }, - { - "columns": [ - { - "definition": "A unique identifier for each product. This ID is used to distinguish individual products.", - "name": "ProductID", - "type": "INT" - }, - { - "definition": "The name of the product. This provides a brief and identifiable name for each product.", - "name": "Name", - "type": "NVARCHAR(50)" - }, - { - "definition": "The model name associated with the product. This indicates the specific model type or version of the product.", - "name": "ProductModel", - "type": "NVARCHAR(50)" - }, - { - "definition": "The culture or language code for the product description. This is used to localize the product description, such as 'en' for English or 'fr' for French.", - "name": "Culture", - "sample_values": [ - "en", - "fr", - "es", - "de" - ], - "type": "NVARCHAR(6)" - }, - { - "definition": "A detailed description of the product. This text provides additional information about the product, which can vary based on the culture or language.", - "name": "Description", - "type": "NVARCHAR(400)" - } - ], - "description": "This view provides detailed information about products, including their names, associated product models, descriptions, and the specific culture or language of the description. It is useful for understanding product details and translating product descriptions for different cultures.", - "entity": "vProductAndDescription", - "selector": "Use this view when you need comprehensive details about products, including their descriptions in different languages. This view is particularly useful for multilingual product catalogs or when creating localized content.", - "view_name": "Product and Description" - }, - { - "columns": [ - { - "definition": "A unique identifier for each product model. This ID is used to distinguish different product models.", - "name": "ProductModelID", - "type": "INT" - }, - { - "definition": "The name of the product model, providing a recognizable title for each model.", - "name": "Name", - "type": "NVARCHAR(50)" - }, - { - "definition": "A brief summary of the product model, highlighting key features and characteristics.", - "name": "Summary", - "type": "NVARCHAR(MAX)" - }, - { - "definition": "The name of the manufacturer of the product model.", - "name": "Manufacturer", - "type": "NVARCHAR(50)" - }, - { - "definition": "Copyright information related to the product model, indicating the legal ownership of the product design and content.", - "name": "Copyright", - "type": "NVARCHAR(30)" - }, - { - "definition": "The URL for the product model, providing a link to more information or to purchase the product.", - "name": "ProductURL", - "type": "NVARCHAR(256)" - }, - { - "definition": "The duration of the warranty period for the product model, specifying how long the warranty is valid.", - "name": "WarrantyPeriod", - "type": "NVARCHAR(30)" - }, - { - "definition": "A description of the warranty provided for the product model, detailing what is covered under the warranty.", - "name": "WarrantyDescription", - "type": "NVARCHAR(255)" - }, - { - "definition": "The number of years the warranty is valid for the product model.", - "name": "NoOfYears", - "type": "INT" - }, - { - "definition": "A description of the maintenance requirements and recommendations for the product model.", - "name": "MaintenanceDescription", - "type": "NVARCHAR(MAX)" - }, - { - "definition": "Details about the type of wheels used in the product model.", - "name": "Wheel", - "type": "NVARCHAR(50)" - }, - { - "definition": "Information about the saddle of the product model, such as material and design.", - "name": "Saddle", - "type": "NVARCHAR(50)" - }, - { - "definition": "Details regarding the pedal design and specifications of the product model.", - "name": "Pedal", - "type": "NVARCHAR(50)" - }, - { - "definition": "Description of the bike frame used in the product model, including material and type.", - "name": "BikeFrame", - "type": "NVARCHAR(50)" - }, - { - "definition": "Information about the crankset of the product model, specifying its design and features.", - "name": "Crankset", - "type": "NVARCHAR(50)" - }, - { - "definition": "The angle at which the product model is photographed, providing a visual perspective of the product.", - "name": "PictureAngle", - "type": "NVARCHAR(20)" - }, - { - "definition": "The size of the product model's picture, specifying dimensions or resolution.", - "name": "PictureSize", - "type": "NVARCHAR(20)" - }, - { - "definition": "An identifier linking to the product photo, which provides a visual representation of the product model.", - "name": "ProductPhotoID", - "type": "INT" - }, - { - "definition": "The material used in the construction of the product model, indicating durability and quality.", - "name": "Material", - "type": "NVARCHAR(50)" - }, - { - "definition": "The color of the product model, providing information about the appearance of the product.", - "name": "Color", - "type": "NVARCHAR(15)" - }, - { - "definition": "A code representing the product line to which the model belongs, categorizing the product within a broader product range.", - "name": "ProductLine", - "type": "NVARCHAR(2)" - }, - { - "definition": "The style of the product model, indicating design and aesthetic aspects.", - "name": "Style", - "type": "NVARCHAR(50)" - }, - { - "definition": "A description of the target rider's experience level for which the product model is designed, such as beginner, intermediate, or expert.", - "name": "RiderExperience", - "type": "NVARCHAR(50)" - }, - { - "definition": "The date and time when the product model information was last modified, indicating the currency of the data.", - "name": "ModifiedDate", - "type": "DATETIME" - } - ], - "description": "This view provides detailed catalog information about product models, including descriptions, manufacturing details, warranty information, and specifications related to product design and features. It is useful for generating comprehensive product catalogs and providing detailed product information to customers.", - "entity": "vProductModelCatalogDescription", - "selector": "Use this view when you need to retrieve detailed product model descriptions, manufacturing information, and specifications. It is particularly useful for creating product catalogs, detailing product features, and providing warranty information.", - "view_name": "Product Model Catalog Description" - } - ] -} diff --git a/text2sql/plugins/sql_plugin/entities_schema.json b/text2sql/plugins/sql_plugin/entities_schema.json deleted file mode 100644 index 27efcff..0000000 --- a/text2sql/plugins/sql_plugin/entities_schema.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "tables": { - "items": { - "additionalProperties": false, - "properties": { - "columns": { - "items": { - "additionalProperties": false, - "properties": { - "allowed_values": { - "description": "Absolute list of values for the column, if applicable.", - "items": { - "type": "string" - }, - "type": "array" - }, - "definition": { - "description": "A brief definition of the column's purpose.", - "type": "string" - }, - "name": { - "description": "The name of the column.", - "type": "string" - }, - "sample_values": { - "description": "Sample values for the column, if applicable.", - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "description": "The data type of the column.", - "type": "string" - } - }, - "required": [ - "name", - "type", - "definition" - ], - "type": "object" - }, - "type": "array" - }, - "description": { - "description": "A brief description of what the table contains.", - "type": "string" - }, - "entity": { - "description": "The technical name of the table entity.", - "type": "string" - }, - "selector": { - "description": "Guidance on when and how to use this table.", - "type": "string" - }, - "table_name": { - "description": "The name of the table.", - "type": "string" - } - }, - "required": [ - "table_name", - "entity", - "description", - "selector", - "columns" - ], - "type": "object" - }, - "type": "array" - }, - "views": { - "items": { - "additionalProperties": false, - "properties": { - "columns": { - "items": { - "additionalProperties": false, - "properties": { - "allowed_values": { - "description": "Absolute list of values for the column, if applicable.", - "items": { - "type": "string" - }, - "type": "array" - }, - "definition": { - "description": "A brief definition of the column's purpose.", - "type": "string" - }, - "name": { - "description": "The name of the column.", - "type": "string" - }, - "sample_values": { - "description": "Sample values for the column, if applicable.", - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "description": "The data type of the column.", - "type": "string" - } - }, - "required": [ - "name", - "type", - "definition" - ], - "type": "object" - }, - "type": "array" - }, - "description": { - "description": "A brief description of what the view contains.", - "type": "string" - }, - "entity": { - "description": "The technical name of the view entity.", - "type": "string" - }, - "selector": { - "description": "Guidance on when and how to use this view.", - "type": "string" - }, - "view_name": { - "description": "The name of the view.", - "type": "string" - } - }, - "required": [ - "view_name", - "entity", - "description", - "selector", - "columns" - ], - "type": "object" - }, - "type": "array" - } - }, - "required": [ - "tables", - "views" - ], - "type": "object" -} diff --git a/text_2_sql/.env b/text_2_sql/.env new file mode 100644 index 0000000..76c7318 --- /dev/null +++ b/text_2_sql/.env @@ -0,0 +1,16 @@ +OpenAI__CompletionDeployment= +OpenAI__EmbeddingModel= +OpenAI__Endpoint= +OpenAI__ApiKey= +OpenAI__ApiVersion= +Text2Sql__DatabaseEngine= +Text2Sql__DatabaseName= +Text2Sql__DatabaseConnectionString= +AIService__AzureSearchOptions__Endpoint= +AIService__AzureSearchOptions__Key= +AIService__AzureSearchOptions__RagDocuments__Index= +AIService__AzureSearchOptions__Text2Sql__Index= +AIService__AzureSearchOptions__RagDocuments__SemanticConfig= +AIService__AzureSearchOptions__Text2Sql__SemanticConfig= +IdentityType= # system_assigned or user_assigned or key +ClientId= diff --git a/text2sql/README.md b/text_2_sql/README.md similarity index 66% rename from text2sql/README.md rename to text_2_sql/README.md index eed7a40..916c2c9 100644 --- a/text2sql/README.md +++ b/text_2_sql/README.md @@ -6,6 +6,8 @@ The implementation is written for [Semantic Kernel](https://github.com/microsoft The sample provided works with Azure SQL Server, although it has been easily adapted to other SQL sources such as Snowflake. +**Two approaches are provided for schema management. A prompt based approach and a vector database based approach. See Multi-Shot Approach for more details** + ## High Level Workflow The following diagram shows a workflow for how the Text2SQL plugin would be incorporated into a RAG application. Using the plugins available, alongside the [Function Calling](https://platform.openai.com/docs/guides/function-calling) capabilities of LLMs, the LLM can do [Chain of Thought](https://learn.microsoft.com/en-us/dotnet/ai/conceptual/chain-of-thought-prompting) reasoning to determine the steps needed to answer the question. This allows the LLM to recognise intent and therefore pick appropriate data sources based on the intent of the question. @@ -34,19 +36,22 @@ To solve these issues, a Multi-Shot approach is used: ![Comparison between a common Text2SQL approach and a Multi-Shot Text2SQL approach.](./images/OneShot%20SQL%20vs%20TwoShot%20SQL%20OpenAI.png "Multi Shot SQL Approach") -Instead of inserting the entire database schema into the prompt, a brief description of the available entities is injected into the prompt. This limits the number of tokens used and avoids filling the prompt with confusing schema information. +To solve this, two different approaches can be used: + - Injection of a brief description of the available entities is injected into the prompt. This limits the number of tokens used and avoids filling the prompt with confusing schema information. + - Indexing the entity definitions in a vector database, such as AI Search, and querying it retrieve the most relevant entities for the query. + +Both approaches limit the number of tokens used and avoids filling the prompt with confusing schema information. Using Auto-Function calling capabilities, the LLM is able to retrieve from the plugin the full schema information for the views / tables that it considers useful for answering the question. Once retrieved, the full SQL query can then be generated. The schemas for multiple views / tables can be retrieved to allow the LLM to perform joins and other complex queries. ## Provided Notebooks -- `./rag_with_text_2_sql.ipynb` provides example of how to utilise the Text2SQL plugin to query the database. -- `./rag_with_ai_searchandtext_2_sql.ipynb` provides an example of how to use the Text2SQL and an AISearch plugin in parallel to automatically retrieve data from the most relevant source to answer the query. +- `./rag_with_prompt_based_text_2_sql.ipynb` provides example of how to utilise the Prompt Based Text2SQL plugin to query the database. +- `./rag_with_vector_based_text_2_sql.ipynb` provides example of how to utilise the Vector Based Text2SQL plugin to query the database. +- `./rag_with_ai_search_and_text_2_sql.ipynb` provides an example of how to use the Text2SQL and an AISearch plugin in parallel to automatically retrieve data from the most relevant source to answer the query. - This setup is useful for a production application as the SQL Database is unlikely to be able to answer all the questions a user may ask. -## SQL Plugin - -`./plugins/sql_plugin` contains all the relevant Semantic Kernel code for the plugin. +## Data Dictionary ### entities.json @@ -56,51 +61,55 @@ The data dictionary is stored in `./plugins/sql_plugin/entities.json`. Below is ```json { - "view_name": "Get All Categories", - "entity": "vGetAllCategories", - "description": "This view provides a comprehensive list of all product categories and their corresponding subcategories in the SalesLT schema of the AdventureWorksLT database. It is used to understand the hierarchical structure of product categories, facilitating product organization and categorization.", - "selector": "Use this view to retrieve information about product categories and subcategories. It is useful for scenarios where product categorization is required, such as generating reports based on product categories or filtering products by category.", - "columns": [ + "EntityName": "Get All Categories", + "Entity": "vGetAllCategories", + "Description": "This view provides a comprehensive list of all product categories and their corresponding subcategories in the SalesLT schema of the AdventureWorksLT database. It is used to understand the hierarchical structure of product categories, facilitating product organization and categorization.", + "Columns": [ { - "definition": "A unique identifier for each product category. This ID is used to reference specific categories.", - "name": "ProductCategoryID", - "type": "INT" + "Definition": "A unique identifier for each product category. This ID is used to reference specific categories.", + "Name": "ProductCategoryID", + "Type": "INT" }, { - "definition": "The name of the parent product category. This represents the top-level category under which subcategories are grouped.", - "name": "ParentProductCategoryName", - "type": "NVARCHAR(50)" + "Definition": "The name of the parent product category. This represents the top-level category under which subcategories are grouped.", + "Name": "ParentProductCategoryName", + "Type": "NVARCHAR(50)" }, { - "definition": "The name of the product category. This can refer to either a top-level category or a subcategory, depending on the context.", - "name": "ProductCategoryName", - "type": "NVARCHAR(50)" + "Definition": "The name of the product category. This can refer to either a top-level category or a subcategory, depending on the context.", + "Name": "ProductCategoryName", + "Type": "NVARCHAR(50)" } ] } ``` #### Property Definitions -- **view_name** or **table_name** is a human readable name for the entity. -- **entity** is the actual name for the entity that is used in the SQL query. -- **description** provides a comprehensive description of what information the entity contains. -- **selector** provides reasoning to the LLM of in which scenarios it should select this entity. -- **columns** contains a list of the columns exposed for querying. Each column contains: - - **definition** a short definition of what information the column contains. Here you can add extra metadata to **prompt engineer** the LLM to select the right columns or interpret the data in the column correctly. - - **name** is the actual column name. - - **type** is the datatype for the column. - - **sample_values (optional)** is a list of sample values that are in the column. This is useful for instructing the LLM of what format the data may be in. - - **allowed_values (optional)** is a list of absolute allowed values for the column. This instructs the LLM only to use these values if filtering against this column. +- **EntityName** is a human readable name for the entity. +- **Entity** is the actual name for the entity that is used in the SQL query. +- **Description** provides a comprehensive description of what information the entity contains. +- **Columns** contains a list of the columns exposed for querying. Each column contains: + - **Definition** a short definition of what information the column contains. Here you can add extra metadata to **prompt engineer** the LLM to select the right columns or interpret the data in the column correctly. + - **Name** is the actual column name. + - **Type** is the datatype for the column. + - **SampleValues (optional)** is a list of sample values that are in the column. This is useful for instructing the LLM of what format the data may be in. + - **AllowedValues (optional)** is a list of absolute allowed values for the column. This instructs the LLM only to use these values if filtering against this column. A full data dictionary must be built for all the views / tables you which to expose to the LLM. The metadata provide directly influences the accuracy of the Text2SQL component. -### sql_plugin.py +## Prompt Based SQL Plugin + +This approach works well for a small number of entities (test on up to 20 entities with hundreds of columns). It performed well on the testing, with correct metadata, we achieved 100% accuracy on the test set. -The `./plugins/sql_plugin/sql_plugin.py` contains 3 key methods to power the Text2SQL engine. +Whilst a simple and high performing approach, the downside of this approach is the increase in number of tokens as the number of entities increases. + +### prompt_based_sql_plugin.py + +The `./plugins/prompt_based_sql_plugin/prompt_based_sql_plugin.py` contains 3 key methods to power the Prompt Based Text2SQL engine. #### system_prompt() -This method takes the loaded `entities.json` file and generates a system prompt based on it. Here, the **entity_name**, **description** and **selector** are used to build a list of available entities for the LLM to select. +This method takes the loaded `entities.json` file and generates a system prompt based on it. Here, the **EntityName** and **Description** are used to build a list of available entities for the LLM to select. This is then inserted into a pre-made Text2SQL generation prompt that already contains optimised and working instructions for the LLM. This system prompt for the plugin is added to the main prompt file at runtime. @@ -114,6 +123,34 @@ This method is called by the Semantic Kernel framework automatically, when instr This method is called by the Semantic Kernel framework automatically, when instructed to do so by the LLM, to run a SQL query against the given database. It returns a JSON string containing a row wise dump of the results returned. These results are then interpreted to answer the question. +## Vector Based SQL Plugin + +This approach allows the system to scale without significantly increasing the number of tokens used within the system prompt. Indexing and running an AI Search instance consumes additional cost, compared to the prompt based approach. + +### vector_based_sql_plugin.py + +The `./plugins/vector_based_sql_plugin/vector_based_sql_plugin.py` contains 3 key methods to power the Vector Based Text2SQL engine. + +#### Indexing + +`./deploy_ai_search/text_2_sql.py` contains the scripts to deploy and index the data dictionary for use within the plugin. See instructions in `./deploy_ai_search/README.md`. + +#### system_prompt() + +This method simply returns a pre-made system prompt that contains optimised and working instructions for the LLM. This system prompt for the plugin is added to the main prompt file at runtime. + +The **target_engine** is passed to the prompt, along with **engine_specific_rules** to ensure that the SQL queries generated work on the target engine. + +#### get_entity_schema() + +This method is called by the Semantic Kernel framework automatically, when instructed to do so by the LLM, to search the AI Search instance with the given text. The LLM is able to pass the key terms from the user query, and retrieve a ranked list of the most suitable entities to answer the question. + +The search text passed is vectorised against the entity level **Description** columns. A hybrid Semantic Reranking search is applied against the **EntityName**, **Entity**, **Columns/Name** fields. + +#### run_sql_query() + +This method is called by the Semantic Kernel framework automatically, when instructed to do so by the LLM, to run a SQL query against the given database. It returns a JSON string containing a row wise dump of the results returned. These results are then interpreted to answer the question. + ## Sample Usage ### What is the top performing product by quantity of units sold? @@ -164,7 +201,7 @@ The top-performing product by quantity of units sold is the **Classic Vest, S** - Give all columns and views / tables good names that are descriptive. - Spend time providing good descriptions in the metadata for all entities and columns e.g. - If a column contains a value in a given currency, give the currency information in the metadata. - - Clearly state in the **selector** what sorts of questions a given view / table can provide answers for. + - Clearly state in the **description** what sorts of questions a given view / table can provide answers for. - Use common codes for columns that need filtering e.g. - A country can have multiple text representations e.g. United Kingdom or UK. Use ISO codes for countries, instead of text descriptions to increase the likelihood of correct and valid SQL queries. @@ -177,10 +214,3 @@ Below are some of the considerations that should be made before using this plugi - Consider limiting the permissions of the identity or connection string to only allow access to certain tables or perform certain query types. - If possible, run the queries under the identity of the end user so that any row or column level security is applied to the data. - Consider data masking for sensitive columns that you do not wish to be exposed. - -## Possible Improvements - -Below are some possible improvements that could be made to the Text2SQL approach: - -- Storing the entity names / definitions / selectors in a vector database and using a vector search to obtain the most relevant entities. - - Due to the small number of tokens that this approaches uses, this approach was not considered but if the number of tables is significantly larger, this approach may provide benefits in selecting the most appropriate tables (untested). diff --git a/text_2_sql/data_dictionary/entities.json b/text_2_sql/data_dictionary/entities.json new file mode 100644 index 0000000..fdd2174 --- /dev/null +++ b/text_2_sql/data_dictionary/entities.json @@ -0,0 +1,384 @@ +[ + { + "Columns": [ + { + "Definition": "A unique identifier for each sales order ticket. This ID is auto-generated and serves as the primary key for the SalesOrderTicket table.", + "Name": "SalesOrderID", + "Type": "INT" + }, + { + "Definition": "The date and time when the sales order was created. This is used to track when the order was initiated.", + "Name": "OrderDate", + "Type": "DATETIME" + }, + { + "Definition": "The date by which the order is expected to be fulfilled or delivered. It helps in managing delivery timelines.", + "Name": "DueDate", + "Type": "DATETIME" + }, + { + "Definition": "The date when the order was shipped to the customer. This is used for tracking shipping and fulfillment status.", + "Name": "ShipDate", + "Type": "DATETIME" + }, + { + "AllowedValues": [ + 1, + 2, + 3 + ], + "Definition": "The current status of the order, represented as a numeric code (e.g., 1 for In Progress, 2 for Completed, 3 for Canceled).", + "Name": "Status", + "Type": "TINYINT" + }, + { + "Definition": "The total amount due for the order, including all line items, taxes, and shipping charges.", + "Name": "TotalDue", + "Type": "MONEY" + }, + { + "Definition": "The date and time when the sales order ticket record was last modified. This is used for tracking updates and changes to the order.", + "Name": "ModifiedDate", + "Type": "DATETIME" + } + ], + "Description": "This table stores detailed information about sales order tickets, including the order details, customer information, order status, and timestamps. It is used to manage and track sales orders throughout the order lifecycle, from creation to fulfillment.", + "Entity": "SalesOrderDetail", + "EntityName": "Sales Order Detail" + }, + { + "Columns": [ + { + "Definition": "A unique identifier for each sales order. This ID is auto-generated and serves as the primary key for the SalesOrderHeader table.", + "Name": "SalesOrderID", + "Type": "INT" + }, + { + "Definition": "The date and time when the sales order was created. This field is used to track when the order was initiated.", + "Name": "OrderDate", + "Type": "DATETIME" + }, + { + "Definition": "The date by which the order is expected to be fulfilled or delivered. It helps in managing delivery timelines.", + "Name": "DueDate", + "Type": "DATETIME" + }, + { + "Definition": "The date when the order was shipped to the customer. This is used for tracking shipping and fulfillment status.", + "Name": "ShipDate", + "Type": "DATETIME" + }, + { + "AllowedValues": [ + 1, + 2, + 3 + ], + "Definition": "The current status of the order, represented as a numeric code (e.g., 1 for In Progress, 2 for Completed, 3 for Canceled).", + "Name": "Status", + "Type": "TINYINT" + }, + { + "AllowedValues": [ + "True", + "False" + ], + "Definition": "Indicates whether the order was placed online.", + "Name": "OnlineOrderFlag", + "Type": "BIT" + }, + { + "Definition": "A unique order number assigned to the sales order. This is used for tracking and identification purposes.", + "Name": "SalesOrderNumber", + "Type": "NVARCHAR(25)" + }, + { + "Definition": "The purchase order number provided by the customer. This field links the sales order to the customer's purchase order.", + "Name": "PurchaseOrderNumber", + "Type": "NVARCHAR(25)" + }, + { + "Definition": "The account number of the customer placing the order. This helps link the order to the customer's account.", + "Name": "AccountNumber", + "Type": "NVARCHAR(15)" + }, + { + "Definition": "A foreign key that links to the Customer table, representing the customer who placed the order.", + "Name": "CustomerID", + "Type": "INT" + }, + { + "Definition": "A foreign key that links to the Address table, representing the shipping address for the order.", + "Name": "ShipToAddressID", + "Type": "INT" + }, + { + "Definition": "A foreign key that links to the Address table, representing the billing address for the order.", + "Name": "BillToAddressID", + "Type": "INT" + }, + { + "Definition": "The shipping method used for the order (e.g., UPS, FedEx). This field helps track shipping preferences.", + "Name": "ShipMethod", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The total cost of the order before taxes and shipping charges. This field is used to calculate the final total. The currency is pound sterling (GBP).", + "Name": "SubTotal", + "Type": "MONEY" + }, + { + "Definition": "The tax amount applied to the order. This is calculated based on the order subtotal and applicable tax rates. The currency is pound sterling (GBP).", + "Name": "TaxAmt", + "Type": "MONEY" + }, + { + "Definition": "The shipping charge applied to the order. This field represents the cost of shipping the order to the customer. The currency is pound sterling (GBP).", + "Name": "Freight", + "Type": "MONEY" + }, + { + "Definition": "The total amount due for the order, including all line items, taxes, and shipping charges. The currency is pound sterling (GBP).", + "Name": "TotalDue", + "Type": "MONEY" + }, + { + "Definition": "Any additional comments or notes related to the sales order. This field can include special instructions or remarks.", + "Name": "Comment", + "Type": "NVARCHAR(255)" + }, + { + "Definition": "The date and time when the sales order header record was last modified. This is used for tracking updates and changes to the order.", + "Name": "ModifiedDate", + "Type": "DATETIME" + } + ], + "Description": "This table contains high-level information about sales orders, including order dates, customer details, shipping information, and order status. It is used to manage and track sales orders from initiation to fulfillment.", + "Entity": "SalesOrderHeader", + "EntityName": "Sales Order Header" + }, + { + "Columns": [ + { + "Definition": "A unique identifier for each address. This ID is auto-generated and serves as the primary key for the Address table.", + "Name": "AddressID", + "Type": "INT" + }, + { + "Definition": "The city in which the address is located. This is used to specify the city for the address.", + "Name": "City", + "Type": "NVARCHAR(30)" + }, + { + "Definition": "The state or province in which the address is located. This is used to specify the state or province for the address.", + "Name": "StateProvince", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The country or region in which the address is located. This is used to specify the country or region for the address.", + "Name": "CountryRegion", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The postal code associated with the address. This is used to specify the postal code for the address, which helps in geographical sorting and shipping.", + "Name": "PostalCode", + "Type": "NVARCHAR(15)" + }, + { + "Definition": "The date and time when the address record was last modified. This is used for tracking updates and changes to the address information.", + "Name": "ModifiedDate", + "Type": "DATETIME" + } + ], + "Description": "This table stores address information for customers, including street addresses, city, state, postal code, and country/region. It is used to maintain contact and shipping information for orders, as well as to manage customer locations.", + "Entity": "Address", + "EntityName": "Address" + }, + { + "Columns": [ + { + "Definition": "A unique identifier for each product category. This ID is used to reference specific categories.", + "Name": "ProductCategoryID", + "Type": "INT" + }, + { + "Definition": "The name of the parent product category. This represents the top-level category under which subcategories are grouped.", + "Name": "ParentProductCategoryName", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The name of the product category. This can refer to either a top-level category or a subcategory, depending on the context.", + "Name": "ProductCategoryName", + "Type": "NVARCHAR(50)" + } + ], + "Description": "This view provides a comprehensive list of all product categories and their corresponding subcategories in the SalesLT schema of the AdventureWorksLT database. It is used to understand the hierarchical structure of product categories, facilitating product organization and categorization.", + "Entity": "vGetAllCategories", + "EntityName": "Get All Categories" + }, + { + "Columns": [ + { + "Definition": "A unique identifier for each product. This ID is used to distinguish individual products.", + "Name": "ProductID", + "Type": "INT" + }, + { + "Definition": "The name of the product. This provides a brief and identifiable name for each product.", + "Name": "Name", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The model name associated with the product. This indicates the specific model type or version of the product.", + "Name": "ProductModel", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The culture or language code for the product description. This is used to localize the product description, such as 'en' for English or 'fr' for French.", + "Name": "Culture", + "SampleValues": [ + "en", + "fr", + "es", + "de" + ], + "Type": "NVARCHAR(6)" + }, + { + "Definition": "A detailed description of the product. This text provides additional information about the product, which can vary based on the culture or language.", + "Name": "Description", + "Type": "NVARCHAR(400)" + } + ], + "Description": "This view provides detailed information about products, including their names, associated product models, descriptions, and the specific culture or language of the description. It is useful for understanding product details and translating product descriptions for different cultures.", + "Entity": "vProductAndDescription", + "EntityName": "Product and Description" + }, + { + "Columns": [ + { + "Definition": "A unique identifier for each product model. This ID is used to distinguish different product models.", + "Name": "ProductModelID", + "Type": "INT" + }, + { + "Definition": "The name of the product model, providing a recognizable title for each model.", + "Name": "Name", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "A brief summary of the product model, highlighting key features and characteristics.", + "Name": "Summary", + "Type": "NVARCHAR(MAX)" + }, + { + "Definition": "The name of the manufacturer of the product model.", + "Name": "Manufacturer", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "Copyright information related to the product model, indicating the legal ownership of the product design and content.", + "Name": "Copyright", + "Type": "NVARCHAR(30)" + }, + { + "Definition": "The URL for the product model, providing a link to more information or to purchase the product.", + "Name": "ProductURL", + "Type": "NVARCHAR(256)" + }, + { + "Definition": "The duration of the warranty period for the product model, specifying how long the warranty is valid.", + "Name": "WarrantyPeriod", + "Type": "NVARCHAR(30)" + }, + { + "Definition": "A description of the warranty provided for the product model, detailing what is covered under the warranty.", + "Name": "WarrantyDescription", + "Type": "NVARCHAR(255)" + }, + { + "Definition": "The number of years the warranty is valid for the product model.", + "Name": "NoOfYears", + "Type": "INT" + }, + { + "Definition": "A description of the maintenance requirements and recommendations for the product model.", + "Name": "MaintenanceDescription", + "Type": "NVARCHAR(MAX)" + }, + { + "Definition": "Details about the type of wheels used in the product model.", + "Name": "Wheel", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "Information about the saddle of the product model, such as material and design.", + "Name": "Saddle", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "Details regarding the pedal design and specifications of the product model.", + "Name": "Pedal", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "Description of the bike frame used in the product model, including material and type.", + "Name": "BikeFrame", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "Information about the crankset of the product model, specifying its design and features.", + "Name": "Crankset", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The angle at which the product model is photographed, providing a visual perspective of the product.", + "Name": "PictureAngle", + "Type": "NVARCHAR(20)" + }, + { + "Definition": "The size of the product model's picture, specifying dimensions or resolution.", + "Name": "PictureSize", + "Type": "NVARCHAR(20)" + }, + { + "Definition": "An identifier linking to the product photo, which provides a visual representation of the product model.", + "Name": "ProductPhotoID", + "Type": "INT" + }, + { + "Definition": "The material used in the construction of the product model, indicating durability and quality.", + "Name": "Material", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The color of the product model, providing information about the appearance of the product.", + "Name": "Color", + "Type": "NVARCHAR(15)" + }, + { + "Definition": "A code representing the product line to which the model belongs, categorizing the product within a broader product range.", + "Name": "ProductLine", + "Type": "NVARCHAR(2)" + }, + { + "Definition": "The style of the product model, indicating design and aesthetic aspects.", + "Name": "Style", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "A description of the target rider's experience level for which the product model is designed, such as beginner, intermediate, or expert.", + "Name": "RiderExperience", + "Type": "NVARCHAR(50)" + }, + { + "Definition": "The date and time when the product model information was last modified, indicating the currency of the data.", + "Name": "ModifiedDate", + "Type": "DATETIME" + } + ], + "Description": "This view provides detailed catalog information about product models, including descriptions, manufacturing details, warranty information, and specifications related to product design and features. It is useful for generating comprehensive product catalogs and providing detailed product information to customers.", + "Entity": "vProductModelCatalogDescription", + "EntityName": "Product Model Catalog Description" + } +] diff --git a/text_2_sql/images/OneShot SQL vs TwoShot SQL OpenAI.png b/text_2_sql/images/OneShot SQL vs TwoShot SQL OpenAI.png new file mode 100644 index 0000000000000000000000000000000000000000..6b9f6e814224bdb7acdd50ee05b34e3839f92421 GIT binary patch literal 223764 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�>MRiox$0J11Z-q}E?#^l= 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 literal 0 HcmV?d00001 diff --git a/text2sql/plugins/ai_search_plugin/__init__.py b/text_2_sql/plugins/ai_search_plugin/__init__.py similarity index 100% rename from text2sql/plugins/ai_search_plugin/__init__.py rename to text_2_sql/plugins/ai_search_plugin/__init__.py diff --git a/text2sql/plugins/ai_search_plugin/ai_search_plugin.py b/text_2_sql/plugins/ai_search_plugin/ai_search_plugin.py similarity index 59% rename from text2sql/plugins/ai_search_plugin/ai_search_plugin.py rename to text_2_sql/plugins/ai_search_plugin/ai_search_plugin.py index f20f532..fdd18c8 100644 --- a/text2sql/plugins/ai_search_plugin/ai_search_plugin.py +++ b/text_2_sql/plugins/ai_search_plugin/ai_search_plugin.py @@ -1,81 +1,113 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -from semantic_kernel.functions import kernel_function -from typing import Annotated -from azure.identity import DefaultAzureCredential -from openai import AsyncAzureOpenAI -from azure.search.documents.models import VectorizedQuery -from azure.search.documents.aio import SearchClient -import os -import json -import logging - - -class AISearchPlugin: - """A plugin that allows for the execution of AI Search queries against a text input.""" - - @staticmethod - def system_prompt() -> str: - """Get the system prompt for the AI Search Plugin.""" - return """Use the AI Search to return documents that have been indexed, that might be relevant for a piece of text to aid understanding. AI Search should always be used, even if you believe it might not be relevant. Execute this in parallel to any other functions that might be relevant.""" - - @kernel_function( - description="Runs an hybrid semantic search against some text to return relevant documents that are indexed within AI Search.", - name="QueryDocumentStorage", - ) - async def query_document_storage( - self, text: Annotated[str, "The text to run a semantic search against."] - ) -> str: - """Sends an text query to AI Search and uses Semantic Ranking to return a result. - - Args: - ---- - text (str): The text to run the search against. - - Returns: - ---- - str: The JSON representation of the search results. - """ - - async with AsyncAzureOpenAI( - # This is the default and can be omitted - api_key=os.environ["OPEN_AI_KEY"], - azure_endpoint=os.environ["OPEN_AI_ENDPOINT"], - api_version=os.environ["OPEN_AI_VERSION"], - ) as open_ai_client: - embeddings = await open_ai_client.embeddings.create( - model=os.environ["OPEN_AI_EMBEDDING_MODEL"], input=text - ) - - # Extract the embedding vector - embedding_vector = embeddings.data[0].embedding - - vector_query = VectorizedQuery( - vector=embedding_vector, - k_nearest_neighbors=5, - fields="chunk_vector", - ) - - credential = DefaultAzureCredential() - async with SearchClient( - endpoint=os.environ["AI_SEARCH_ENDPOINT"], - index_name=os.environ["AI_SEARCH_INDEX"], - credential=credential, - ) as search_client: - results = await search_client.search( - top=5, - query_type="semantic", - semantic_configuration_name=os.environ["AI_SEARCH_SEMANTIC_CONFIG"], - search_text=text, - select="title,chunk,source", - vector_queries=[vector_query], - ) - - documents = [ - document - async for result in results.by_page() - async for document in result - ] - - logging.debug("Results: %s", documents) - return json.dumps(documents, default=str) +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +from semantic_kernel.functions import kernel_function +from typing import Annotated +from azure.identity import DefaultAzureCredential +from openai import AsyncAzureOpenAI +from azure.core.credentials import AzureKeyCredential +from azure.search.documents.models import VectorizedQuery +from azure.search.documents.aio import SearchClient +import os +import json +import logging +from enum import Enum + + +class IdentityType(Enum): + """The type of the indexer""" + + USER_ASSIGNED = "user_assigned" + SYSTEM_ASSIGNED = "system_assigned" + KEY = "key" + + +class AISearchPlugin: + """A plugin that allows for the execution of AI Search queries against a text input.""" + + @staticmethod + def system_prompt() -> str: + """Get the system prompt for the AI Search Plugin.""" + return """Use the AI Search to return documents that have been indexed, that might be relevant for a piece of text to aid understanding. AI Search should always be used, even if you believe it might not be relevant. Execute this in parallel to any other functions that might be relevant.""" + + @kernel_function( + description="Runs an hybrid semantic search against some text to return relevant documents that are indexed within AI Search.", + name="QueryDocumentStorage", + ) + async def query_document_storage( + self, text: Annotated[str, "The text to run a semantic search against."] + ) -> str: + """Sends an text query to AI Search and uses Semantic Ranking to return a result. + + Args: + ---- + text (str): The text to run the search against. + + Returns: + ---- + str: The JSON representation of the search results. + """ + + identity = os.environ.get("IdentityType").lower() + + if identity == "user_assigned": + identity_type = IdentityType.USER_ASSIGNED + elif identity == "system_assigned": + identity_type = IdentityType.SYSTEM_ASSIGNED + elif identity == "key": + identity_type = IdentityType.KEY + else: + raise ValueError("Invalid identity type") + + async with AsyncAzureOpenAI( + # This is the default and can be omitted + api_key=os.environ["OpenAI__ApiKey"], + azure_endpoint=os.environ["OpenAI__Endpoint"], + api_version=os.environ["OpenAI__ApiVersion"], + ) as open_ai_client: + embeddings = await open_ai_client.embeddings.create( + model=os.environ["OpenAI__EmbeddingModel"], input=text + ) + + # Extract the embedding vector + embedding_vector = embeddings.data[0].embedding + + vector_query = VectorizedQuery( + vector=embedding_vector, + k_nearest_neighbors=5, + fields="ChunkEmbedding", + ) + + if identity_type == IdentityType.SYSTEM_ASSIGNED: + credential = DefaultAzureCredential() + elif identity_type == IdentityType.USER_ASSIGNED: + credential = DefaultAzureCredential( + managed_identity_client_id=os.environ["ClientID"] + ) + else: + credential = AzureKeyCredential( + os.environ["AIService__AzureSearchOptions__Key"] + ) + async with SearchClient( + endpoint=os.environ["AIService__AzureSearchOptions__Endpoint"], + index_name=os.environ["AIService__AzureSearchOptions__RagDocuments__Index"], + credential=credential, + ) as search_client: + results = await search_client.search( + top=5, + query_type="semantic", + semantic_configuration_name=os.environ[ + "AIService__AzureSearchOptions__RagDocuments__SemanticConfig" + ], + search_text=text, + select="Title,Chunk,SourceUri", + vector_queries=[vector_query], + ) + + documents = [ + document + async for result in results.by_page() + async for document in result + ] + + logging.debug("Results: %s", documents) + return json.dumps(documents, default=str) diff --git a/text2sql/plugins/sql_plugin/__init__.py b/text_2_sql/plugins/prompt_based_sql_plugin/__init__.py similarity index 100% rename from text2sql/plugins/sql_plugin/__init__.py rename to text_2_sql/plugins/prompt_based_sql_plugin/__init__.py diff --git a/text2sql/plugins/sql_plugin/sql_plugin.py b/text_2_sql/plugins/prompt_based_sql_plugin/prompt_based_sql_plugin.py similarity index 76% rename from text2sql/plugins/sql_plugin/sql_plugin.py rename to text_2_sql/plugins/prompt_based_sql_plugin/prompt_based_sql_plugin.py index 26dfc9e..f75a5da 100644 --- a/text2sql/plugins/sql_plugin/sql_plugin.py +++ b/text_2_sql/plugins/prompt_based_sql_plugin/prompt_based_sql_plugin.py @@ -1,161 +1,144 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -from semantic_kernel.functions import kernel_function -import aioodbc -from typing import Annotated -import os -import json -import logging - - -class SQLPlugin: - """A plugin that allows for the execution of SQL queries against a SQL Database.""" - - def __init__(self, database: str, target_engine: str = "Microsoft TSQL Server"): - """Initialize the SQL Plugin. - - Args: - ---- - database (str): The name of the database to connect to. - target_engine (str): The target database engine to run the queries against. Default is 'SQL Server'. - """ - self.entities = {} - self.database = database - self.target_engine = target_engine - - self.load_entities() - - def load_entities(self): - """Load the views from the JSON file and formats into common memory dictionary.""" - with open("./plugins/sql_plugin/entities.json", "r", encoding="utf-8") as file: - entities = json.load(file) - - # Load views - for view in entities["views"]: - entity_object = view.copy() - - entity_object["entity_name"] = entity_object["view_name"] - del entity_object["view_name"] - entity = entity_object["entity"] - entity_object["select_from_entity"] = f"{self.database}.{entity}" - self.entities[entity_object["entity_name"].lower()] = entity_object - - # Load tables - for table in entities["tables"]: - entity_object = table.copy() - - entity_object["entity_name"] = entity_object["table_name"] - del entity_object["table_name"] - entity = entity_object["entity"] - entity_object["select_from_entity"] = f"{self.database}.{entity}" - self.entities[entity_object["entity_name"].lower()] = entity_object - - def system_prompt(self, engine_specific_rules: str | None = None) -> str: - """Get the schemas for the database entities and provide a system prompt for the user. - - Returns: - str: The system prompt for the user. - """ - - entity_descriptions = [] - for entity in self.entities.values(): - entity_string = " [BEGIN ENTITY = '{}']\n Name='{}'\n Description='{} {}'\n [END ENTITY = '{}']".format( - entity["entity_name"].upper(), - entity["entity_name"], - entity["description"], - entity["selector"], - entity["entity_name"].upper(), - ) - entity_descriptions.append(entity_string) - - entity_descriptions = "\n\n ".join(entity_descriptions) - - if engine_specific_rules: - engine_specific_rules = f"\n The following {self.target_engine} Syntax rules must be adhered to.\n {engine_specific_rules}" - - system_prompt = f"""Use the names and descriptions of {self.target_engine} entities provided in ENTITIES LIST to decide which entities to query if you need to retrieve information from the database. Use the 'GetEntitySchema()' function to get more details of the schema of the view you want to query. Use the 'RunSQLQuery()' function to run the SQL query against the database. - - You must always examine the provided {self.target_engine} entity descriptions to determine if they can answer the question. - - [BEGIN ENTITIES LIST] - {entity_descriptions} - [END ENTITIES LIST] - - Output corresponding text values in the answer for columns where there is an ID. For example, if the column is 'ProductID', output the corresponding 'ProductModel' in the response. Do not include the ID in the response. - If a user is asking for a comparison, always compare the relevant values in the database. - - The target database engine is {self.target_engine}, SQL queries must be able compatible to run on {self.target_engine}. {engine_specific_rules} - Always generate the SQL query based on the GetEntitySchema() function output, do not use the chat history data to generate the SQL query. - Do not use any other entities and columns in your SQL query, other than those defined above. Only use the column names obtained from GetEntitySchema() when constructing a SQL query, do not make up column names. - You must only provide SELECT SQL queries. - For a given entity, use the 'select_from_entity' property returned from 'GetEntitySchema()' function in the SELECT FROM part of the SQL query. If the property is {{'select_from_entity': 'test_schema.test_table'}}, the select statement will be formulated from 'SELECT FROM test_schema.test_table WHERE . - - If you don't know how the value is formatted in a column, run a query against the column to get the unique values that might match your query. - Some columns returned from 'GetEntitySchema()' may have the properties 'allowed_values' or 'sample_values'. Use these values to determine the possible values that can be used in the SQL query. - - The source title to cite is the 'entity_name' property. The source reference is the SQL query used. The source chunk is the result of the SQL query used to answer the user query in Markdown table format. e.g. {{ 'title': "vProductAndDescription", 'chunk': '| ProductID | Name | ProductModel | Culture | Description |\\n|-----------|-------------------|--------------|---------|----------------------------------|\\n| 101 | Mountain Bike | MT-100 | en | A durable bike for mountain use. |\\n| 102 | Road Bike | RB-200 | en | Lightweight bike for road use. |\\n| 103 | Hybrid Bike | HB-300 | fr | Vélo hybride pour usage mixte. |\\n', 'reference': 'SELECT ProductID, Name, ProductModel, Culture, Description FROM vProductAndDescription WHERE Culture = \"en\";' }}""" - - return system_prompt - - @kernel_function( - description="Get the detailed schema of an entity in the Database. Use the entity and the column returned to formulate a SQL query. The view name or table name must be one of the ENTITY NAMES defined in the [ENTITIES LIST]. Only use the column names obtained from GetEntitySchema() when constructing a SQL query, do not make up column names.", - name="GetEntitySchema", - ) - async def get_entity_schema( - self, - entity_name: Annotated[ - str, - "The view or table name to get the schema for. It must be one of the ENTITY NAMES defined in the [ENTITIES LIST] function.", - ], - ) -> str: - """Get the schema of a view or table in the SQL Database. - - Args: - ---- - entity_name (str): A views or table name to get the schema for. - - Returns: - str: The schema of the views or tables in JSON format. - """ - - if entity_name.lower() not in self.entities: - return json.dumps( - { - "error": f"The view or table {entity_name} does not exist in the database. Refer to the previously provided list of entities. Allow values are: {', '.join(self.entities.keys())}." - } - ) - - return json.dumps({entity_name: self.entities[entity_name.lower()]}) - - @kernel_function( - description="Runs an SQL query against the SQL Database to extract information.", - name="RunSQLQuery", - ) - async def run_sql_query( - self, sql_query: Annotated[str, "The SQL query to run against the DB"] - ) -> str: - """Sends an SQL Query to the SQL Databases and returns to the result. - - Args: - ---- - sql_query (str): The query to run against the DB. - - Returns: - str: The JSON representation of the query results.""" - - logging.info("Executing SQL Query") - logging.debug("SQL Query: %s", sql_query) - - connection_string = os.environ["SQL_DB_CONNECTION_STRING"] - async with await aioodbc.connect(dsn=connection_string) as sql_db_client: - async with sql_db_client.cursor() as cursor: - await cursor.execute(sql_query) - - columns = [column[0] for column in cursor.description] - - rows = await cursor.fetchall() - results = [dict(zip(columns, returned_row)) for returned_row in rows] - - logging.debug("Results: %s", results) - - return json.dumps(results, default=str) +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +from semantic_kernel.functions import kernel_function +import aioodbc +from typing import Annotated +import os +import json +import logging + + +class PromptBasedSQLPlugin: + """A plugin that allows for the execution of SQL queries against a SQL Database.""" + + def __init__(self, database: str, target_engine: str = "Microsoft TSQL Server"): + """Initialize the SQL Plugin. + + Args: + ---- + database (str): The name of the database to connect to. + target_engine (str): The target database engine to run the queries against. Default is 'SQL Server'. + """ + self.entities = {} + self.database = database + self.target_engine = target_engine + + self.load_entities() + + def load_entities(self): + """Load the views from the JSON file and formats into common memory dictionary.""" + with open("./data_dictionary/entities.json", "r", encoding="utf-8") as file: + entities = json.load(file) + for entity_object in entities: + entity = entity_object["Entity"] + entity_object["SelectFromEntity"] = f"{self.database}.{entity}" + self.entities[entity.lower()] = entity_object + + def system_prompt(self, engine_specific_rules: str | None = None) -> str: + """Get the schemas for the database entities and provide a system prompt for the user. + + Returns: + str: The system prompt for the user. + """ + + entity_descriptions = [] + for entity in self.entities.values(): + entity_string = " [BEGIN ENTITY = '{}']\n Name='{}'\n Description='{}'\n [END ENTITY = '{}']".format( + entity["EntityName"].upper(), + entity["EntityName"], + entity["Description"], + entity["EntityName"].upper(), + ) + entity_descriptions.append(entity_string) + + entity_descriptions = "\n\n ".join(entity_descriptions) + + if engine_specific_rules: + engine_specific_rules = f"\n The following {self.target_engine} Syntax rules must be adhered to.\n {engine_specific_rules}" + + system_prompt = f"""Use the names and descriptions of {self.target_engine} entities provided in ENTITIES LIST to decide which entities to query if you need to retrieve information from the database. Use the 'GetEntitySchema()' function to get more details of the schema of the view you want to query. Use the 'RunSQLQuery()' function to run the SQL query against the database. + + You must always examine the provided {self.target_engine} entity descriptions to determine if they can answer the question. + + [BEGIN ENTITIES LIST] + {entity_descriptions} + [END ENTITIES LIST] + + Output corresponding text values in the answer for columns where there is an ID. For example, if the column is 'ProductID', output the corresponding 'ProductModel' in the response. Do not include the ID in the response. + If a user is asking for a comparison, always compare the relevant values in the database. + + The target database engine is {self.target_engine}, SQL queries must be able compatible to run on {self.target_engine}. {engine_specific_rules} + Always generate the SQL query based on the GetEntitySchema() function output, do not use the chat history data to generate the SQL query. + Do not use any other entities and columns in your SQL query, other than those defined above. Only use the column names obtained from GetEntitySchema() when constructing a SQL query, do not make up column names. + You must only provide SELECT SQL queries. + For a given entity, use the 'SelectFromEntity' property returned from 'GetEntitySchema()' function in the SELECT FROM part of the SQL query. If the property is {{'SelectFromEntity': 'test_schema.test_table'}}, the select statement will be formulated from 'SELECT FROM test_schema.test_table WHERE . + + If you don't know how the value is formatted in a column, run a query against the column to get the unique values that might match your query. + Some columns returned from 'GetEntitySchema()' may have the properties 'AllowedValues' or 'SampleValues'. Use these values to determine the possible values that can be used in the SQL query. + + The source title to cite is the 'entity_name' property. The source reference is the SQL query used. The source chunk is the result of the SQL query used to answer the user query in Markdown table format. e.g. {{ 'title': "vProductAndDescription", 'chunk': '| ProductID | Name | ProductModel | Culture | Description |\\n|-----------|-------------------|--------------|---------|----------------------------------|\\n| 101 | Mountain Bike | MT-100 | en | A durable bike for mountain use. |\\n| 102 | Road Bike | RB-200 | en | Lightweight bike for road use. |\\n| 103 | Hybrid Bike | HB-300 | fr | Vélo hybride pour usage mixte. |\\n', 'reference': 'SELECT ProductID, Name, ProductModel, Culture, Description FROM vProductAndDescription WHERE Culture = \"en\";' }}""" + + return system_prompt + + @kernel_function( + description="Get the detailed schema of an entity in the Database. Use the entity and the column returned to formulate a SQL query. The view name or table name must be one of the ENTITY NAMES defined in the [ENTITIES LIST]. Only use the column names obtained from GetEntitySchema() when constructing a SQL query, do not make up column names.", + name="GetEntitySchema", + ) + async def get_entity_schema( + self, + entity_name: Annotated[ + str, + "The view or table name to get the schema for. It must be one of the ENTITY NAMES defined in the [ENTITIES LIST] function.", + ], + ) -> str: + """Get the schema of a view or table in the SQL Database. + + Args: + ---- + entity_name (str): A views or table name to get the schema for. + + Returns: + str: The schema of the views or tables in JSON format. + """ + + if entity_name.lower() not in self.entities: + return json.dumps( + { + "error": f"The view or table {entity_name} does not exist in the database. Refer to the previously provided list of entities. Allow values are: {', '.join(self.entities.keys())}." + } + ) + + return json.dumps({entity_name: self.entities[entity_name.lower()]}) + + @kernel_function( + description="Runs an SQL query against the SQL Database to extract information.", + name="RunSQLQuery", + ) + async def run_sql_query( + self, sql_query: Annotated[str, "The SQL query to run against the DB"] + ) -> str: + """Sends an SQL Query to the SQL Databases and returns to the result. + + Args: + ---- + sql_query (str): The query to run against the DB. + + Returns: + str: The JSON representation of the query results.""" + + logging.info("Executing SQL Query") + logging.debug("SQL Query: %s", sql_query) + + connection_string = os.environ["Text2Sql__DatabaseConnectionString"] + async with await aioodbc.connect(dsn=connection_string) as sql_db_client: + async with sql_db_client.cursor() as cursor: + await cursor.execute(sql_query) + + columns = [column[0] for column in cursor.description] + + rows = await cursor.fetchall() + results = [dict(zip(columns, returned_row)) for returned_row in rows] + + logging.debug("Results: %s", results) + + return json.dumps(results, default=str) diff --git a/text_2_sql/plugins/vector_based_sql_plugin/__init__.py b/text_2_sql/plugins/vector_based_sql_plugin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/text_2_sql/plugins/vector_based_sql_plugin/vector_based_sql_plugin.py b/text_2_sql/plugins/vector_based_sql_plugin/vector_based_sql_plugin.py new file mode 100644 index 0000000..0a4930a --- /dev/null +++ b/text_2_sql/plugins/vector_based_sql_plugin/vector_based_sql_plugin.py @@ -0,0 +1,190 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +from semantic_kernel.functions import kernel_function +import aioodbc +from typing import Annotated +import os +import json +from azure.core.credentials import AzureKeyCredential +import logging +from azure.identity import DefaultAzureCredential +from openai import AsyncAzureOpenAI +from azure.search.documents.models import VectorizedQuery +from azure.search.documents.aio import SearchClient +from enum import Enum + + +class IdentityType(Enum): + """The type of the indexer""" + + USER_ASSIGNED = "user_assigned" + SYSTEM_ASSIGNED = "system_assigned" + KEY = "key" + + +class VectorBasedSQLPlugin: + """A plugin that allows for the execution of SQL queries against a SQL Database. + + This is an improved version of the SQLPlugin that uses a vector-based approach to generate SQL queries. This works best for a database with a large number of entities and columns. + """ + + def __init__(self, database: str, target_engine: str = "Microsoft TSQL Server"): + """Initialize the SQL Plugin. + + Args: + ---- + database (str): The name of the database to connect to. + target_engine (str): The target database engine to run the queries against. Default is 'SQL Server'. + """ + self.entities = {} + self.database = database + self.target_engine = target_engine + + def system_prompt(self, engine_specific_rules: str | None = None) -> str: + """Get the schemas for the database entities and provide a system prompt for the user. + + Returns: + str: The system prompt for the user. + """ + + if engine_specific_rules: + engine_specific_rules = f"\n The following {self.target_engine} Syntax rules must be adhered to.\n {engine_specific_rules}" + + system_prompt = f"""Use the 'GetEntitySchema()' function to search for the most relevant schemas for the data that you wish to obtain. Use the 'RunSQLQuery()' function to run the SQL query against the database. + + Output corresponding text values in the answer for columns where there is an ID. For example, if the column is 'ProductID', output the corresponding 'ProductModel' in the response. Do not include the ID in the response. + If a user is asking for a comparison, always compare the relevant values in the database. + + The target database engine is {self.target_engine}, SQL queries must be able compatible to run on {self.target_engine}. {engine_specific_rules} + Always generate the SQL query based on the GetEntitySchema() function output, do not use the chat history data to generate the SQL query. + Only use the column names obtained from GetEntitySchema() when constructing a SQL query, do not make up column names. + You must only provide SELECT SQL queries. + For a given entity, use the 'SelectFromEntity' property returned from 'GetEntitySchema()' function in the SELECT FROM part of the SQL query. If the property is {{'SelectFromEntity': 'test_schema.test_table'}}, the select statement will be formulated from 'SELECT FROM test_schema.test_table WHERE . + + If you don't know how the value is formatted in a column, run a query against the column to get the unique values that might match your query. + Some columns returned from 'GetEntitySchema()' may have the properties 'AllowedValues' or 'SampleValues'. Use these values to determine the possible values that can be used in the SQL query. + + The source title to cite is the 'EntityName' property. The source reference is the SQL query used. The source chunk is the result of the SQL query used to answer the user query in Markdown table format. e.g. {{ 'title': "vProductAndDescription", 'chunk': '| ProductID | Name | ProductModel | Culture | Description |\\n|-----------|-------------------|--------------|---------|----------------------------------|\\n| 101 | Mountain Bike | MT-100 | en | A durable bike for mountain use. |\\n| 102 | Road Bike | RB-200 | en | Lightweight bike for road use. |\\n| 103 | Hybrid Bike | HB-300 | fr | Vélo hybride pour usage mixte. |\\n', 'reference': 'SELECT ProductID, Name, ProductModel, Culture, Description FROM vProductAndDescription WHERE Culture = \"en\";' }}""" + + return system_prompt + + @kernel_function( + description="Gets the schema of a view or table in the SQL Database by selecting the most relevant entity based on the search term. Several entities may be returned.", + name="GetEntitySchema", + ) + async def get_entity_schemas( + self, + text: Annotated[ + str, + "The text to run a semantic search against. Relevant entities will be returned.", + ], + ) -> str: + """Gets the schema of a view or table in the SQL Database by selecting the most relevant entity based on the search term. Several entities may be returned. + + Args: + ---- + text (str): The text to run the search against. + + Returns: + str: The schema of the views or tables in JSON format. + """ + + identity = os.environ.get("IdentityType").lower() + + if identity == "user_assigned": + identity_type = IdentityType.USER_ASSIGNED + elif identity == "system_assigned": + identity_type = IdentityType.SYSTEM_ASSIGNED + elif identity == "key": + identity_type = IdentityType.KEY + else: + raise ValueError("Invalid identity type") + + async with AsyncAzureOpenAI( + # This is the default and can be omitted + api_key=os.environ["OpenAI__ApiKey"], + azure_endpoint=os.environ["OpenAI__Endpoint"], + api_version=os.environ["OpenAI__ApiVersion"], + ) as open_ai_client: + embeddings = await open_ai_client.embeddings.create( + model=os.environ["OpenAI__EmbeddingModel"], input=text + ) + + # Extract the embedding vector + embedding_vector = embeddings.data[0].embedding + + vector_query = VectorizedQuery( + vector=embedding_vector, + k_nearest_neighbors=5, + fields="DescriptionEmbedding", + ) + + if identity_type == IdentityType.SYSTEM_ASSIGNED: + credential = DefaultAzureCredential() + elif identity_type == IdentityType.USER_ASSIGNED: + credential = DefaultAzureCredential( + managed_identity_client_id=os.environ["ClientID"] + ) + else: + credential = AzureKeyCredential( + os.environ["AIService__AzureSearchOptions__Key"] + ) + + async with SearchClient( + endpoint=os.environ["AIService__AzureSearchOptions__Endpoint"], + index_name=os.environ["AIService__AzureSearchOptions__Text2Sql__Index"], + credential=credential, + ) as search_client: + results = await search_client.search( + top=3, + query_type="semantic", + semantic_configuration_name=os.environ[ + "AIService__AzureSearchOptions__Text2Sql__SemanticConfig" + ], + search_text=text, + select="Entity,EntityName,Description,Columns", + vector_queries=[vector_query], + ) + + entities = [] + # Add the SelectFromEntity property to the entity + async for result in results.by_page(): + async for entity in result: + entity["SelectFromEntity"] = f"{self.database}.{entity['Entity']}" + entities.append(entity) + + logging.debug("Results: %s", entities) + return json.dumps(entities, default=str) + + @kernel_function( + description="Runs an SQL query against the SQL Database to extract information.", + name="RunSQLQuery", + ) + async def run_sql_query( + self, sql_query: Annotated[str, "The SQL query to run against the DB"] + ) -> str: + """Sends an SQL Query to the SQL Databases and returns to the result. + + Args: + ---- + sql_query (str): The query to run against the DB. + + Returns: + str: The JSON representation of the query results.""" + + logging.info("Executing SQL Query") + logging.debug("SQL Query: %s", sql_query) + + connection_string = os.environ["Text2Sql__DatabaseConnectionString"] + async with await aioodbc.connect(dsn=connection_string) as sql_db_client: + async with sql_db_client.cursor() as cursor: + await cursor.execute(sql_query) + + columns = [column[0] for column in cursor.description] + + rows = await cursor.fetchall() + results = [dict(zip(columns, returned_row)) for returned_row in rows] + + logging.debug("Results: %s", results) + + return json.dumps(results, default=str) diff --git a/text2sql/prompt.yaml b/text_2_sql/prompt.yaml similarity index 97% rename from text2sql/prompt.yaml rename to text_2_sql/prompt.yaml index 4479054..6d15cd3 100644 --- a/text2sql/prompt.yaml +++ b/text_2_sql/prompt.yaml @@ -1,111 +1,113 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -template_format: semantic-kernel -template: | - - As a senior analyst, your primary responsibility is to provide precise and thorough answers to the user's queries. Utilize all the provided functions to craft your responses. You must deliver detailed and accurate final answers with clear explanations and actionable insights. - - Always use the provided functions to obtain key information in order to answer the question. - If you are asked to use always use a function, you must use that function to compliment the answer. - Always use multiple functions to formulate the answer. - Always execute multiple functions in parallel to compliment the results. - - The response to the user must meet the requirements in RESPONSE OUTPUT REQUIREMENTS. - IMPORTANT INFORMATION contains useful information that you can use to aid your knowledge. - CHAT HISTORY contains the previous question and answer pairs in the conversation in JSON format. Do not use this information to answer the question, but to provide context on what was asked previously. - - [IMPORTANT INFORMATION] - - {{$important_information}} - - [END IMPORTANT INFORMATION] - - [RESPONSE OUTPUT REQUIREMENTS] - - The answer MUST be returned in JSON format as { "answer": "", "sources": [ {"title": , "chunk": , "reference": ""}, {"title": , "chunk": , "reference": ""} ] }. - - The 'answer' property MUST meet the requirements in the ANSWER PROPERTY REQUIREMENTS. - The 'sources' property MUST meet the requirements in the SOURCES PROPERTY REQUIREMENTS. - - Do NOT return anything outside of the provided JSON property. - - [ANSWER PROPERTY REQUIREMENTS] - 1. Language and Tone: - Use only British English throughout the response. - Employ a business-friendly language that is professional and easy to understand. - - 2. Content Restrictions: - Do not use any profanity, offensive language, hate speech, or code in the response. - If you encounter any such content, handle it gracefully by omitting or rephrasing it appropriately. - - 3. Information Sources: - Use only information from the provided functions and specified important information. - Do not use any external sources or the chat history for constructing the response. - In case of conflicting information, prioritize data from the SQL Database as the primary source of truth. - - 4. Calculations: - For any required calculations, use only the values provided in the context. - Provide a brief, clear explanation of the calculations beneath the results. - - 5. Response Structure: - Ensure the response is direct, easy to understand, and well-structured. - Format the response using Markdown for clarity and readability. - Use bold sub-headings for clarity where needed. Only use Markdown headings Level 3 (###) and Level 4 (####). - Use bullet points or numbered lists when appropriate. - Do not vary the font size within the same sentence. - - 6. Citations: - All factual information used in the answer must be cited with numbered references. For example, [1] should be used to refer to the first source. - Each citation in the answer must correspond to a single entry in the 'sources' object. - The same citation and corresponding context chunk may be used multiple times if needed. - Place the numbered citation at the end of each relevant sentence that uses information from the sources. - Ensure that each source listed in the 'sources' property is cited at least once in the answer. - Do not provide a list of definitions from the business glossary; use such information only to enhance the answer contextually. - - 7. Citations Format: - Citations should be embedded within the text, not as a separate list at the end of the 'answer' property. - [END ANSWER PROPERTY REQUIREMENTS] - - [SOURCES PROPERTY REQUIREMENTS] - 1. Reference Inclusion: - Include all corresponding references for all cited content in the 'answer' property. - Place the references in the 'sources' property. - - 2. Source Format: - Each entry in the 'sources' property must be formatted as: {"title": "", "chunk": "", "reference": ""} - For example, a complete response with two citations would be formatted as: { "answer": "", "sources": [ {"title": , "chunk": , "reference": ""}, {"title": , "chunk": , "reference": ""} ] } - - 3. Source Chunk: - The 'chunk' property should contain a concise, unedited snippet of the relevant context that supports the answer. - - 4. Mandatory References: - Ensure that every citation in the 'answer' has a corresponding entry in the 'sources' property. - Every entry in the 'sources' property must be cited at least once in the answer. - [END SOURCES PROPERTY REQUIREMENTS] - - [END RESPONSE OUTPUT REQUIREMENTS] - - {{$chat_history}} - {{$user_input}} -description: Chatbot -name: ChatBot -input_variables: - - name: user_input - description: The user input - is_required: true - - name: important_information - description: Useful information for the chatbot - is_required: true -output_variable: - description: The chatbot response formatted in JSON as defined in the FINAL ANSWER OUTPUT REQUIREMENTS. -execution_settings: - chat: - function_choice_behavior: - type: auto - maximum_auto_invoke_attempts: 5 - filters: - excluded_plugins: - - ChatBot - response_format: - type: json_object - temperature: 0.5 +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +template_format: semantic-kernel +template: | + + As a senior analyst, your primary responsibility is to provide precise and thorough answers to the user's queries. Utilize all the provided functions to craft your responses. You must deliver detailed and accurate final answers with clear explanations and actionable insights. + + Always use the provided functions to obtain key information in order to answer the question. + If you are asked to use always use a function, you must use that function to compliment the answer. + Always use multiple functions to formulate the answer. + Always execute multiple functions in parallel to compliment the results. + + The response to the user must meet the requirements in RESPONSE OUTPUT REQUIREMENTS. + IMPORTANT INFORMATION contains useful information that you can use to aid your knowledge. + CHAT HISTORY contains the previous question and answer pairs in the conversation in JSON format. Do not use this information to answer the question, but to provide context on what was asked previously. + + [IMPORTANT INFORMATION] + + {{$important_information}} + + [END IMPORTANT INFORMATION] + + [RESPONSE OUTPUT REQUIREMENTS] + + The answer MUST be returned in JSON format as { "answer": "", "sources": [ {"title": , "chunk": , "reference": ""}, {"title": , "chunk": , "reference": ""} ] }. + + The 'answer' property MUST meet the requirements in the ANSWER PROPERTY REQUIREMENTS. + The 'sources' property MUST meet the requirements in the SOURCES PROPERTY REQUIREMENTS. + + Do NOT return anything outside of the provided JSON property. Ensure that this is valid JSON returned. + + Never return an empty response or null value. Always answer the question. + + [ANSWER PROPERTY REQUIREMENTS] + 1. Language and Tone: + Use only British English throughout the response. + Employ a business-friendly language that is professional and easy to understand. + + 2. Content Restrictions: + Do not use any profanity, offensive language, hate speech, or code in the response. + If you encounter any such content, handle it gracefully by omitting or rephrasing it appropriately. + + 3. Information Sources: + Use only information from the provided functions and specified important information. + Do not use any external sources or the chat history for constructing the response. + In case of conflicting information, prioritize data from the SQL Database as the primary source of truth. + + 4. Calculations: + For any required calculations, use only the values provided in the context. + Provide a brief, clear explanation of the calculations beneath the results. + + 5. Response Structure: + Ensure the response is direct, easy to understand, and well-structured. + Format the response using Markdown for clarity and readability. + Use bold sub-headings for clarity where needed. Only use Markdown headings Level 3 (###) and Level 4 (####). + Use bullet points or numbered lists when appropriate. + Do not vary the font size within the same sentence. + + 6. Citations: + All factual information used in the answer must be cited with numbered references. For example, [1] should be used to refer to the first source. + Each citation in the answer must correspond to a single entry in the 'sources' object. + The same citation and corresponding context chunk may be used multiple times if needed. + Place the numbered citation at the end of each relevant sentence that uses information from the sources. + Ensure that each source listed in the 'sources' property is cited at least once in the answer. + Do not provide a list of definitions from the business glossary; use such information only to enhance the answer contextually. + + 7. Citations Format: + Citations should be embedded within the text, not as a separate list at the end of the 'answer' property. + [END ANSWER PROPERTY REQUIREMENTS] + + [SOURCES PROPERTY REQUIREMENTS] + 1. Reference Inclusion: + Include all corresponding references for all cited content in the 'answer' property. + Place the references in the 'sources' property. + + 2. Source Format: + Each entry in the 'sources' property must be formatted as: {"title": "", "chunk": "", "reference": ""} + For example, a complete response with two citations would be formatted as: { "answer": "", "sources": [ {"title": , "chunk": , "reference": ""}, {"title": , "chunk": , "reference": ""} ] } + + 3. Source Chunk: + The 'chunk' property should contain a concise, unedited snippet of the relevant context that supports the answer. + + 4. Mandatory References: + Ensure that every citation in the 'answer' has a corresponding entry in the 'sources' property. + Every entry in the 'sources' property must be cited at least once in the answer. + [END SOURCES PROPERTY REQUIREMENTS] + + [END RESPONSE OUTPUT REQUIREMENTS] + + {{$chat_history}} + {{$user_input}} +description: Chatbot +name: ChatBot +input_variables: + - name: user_input + description: The user input + is_required: true + - name: important_information + description: Useful information for the chatbot + is_required: true +output_variable: + description: The chatbot response formatted in JSON as defined in the FINAL ANSWER OUTPUT REQUIREMENTS. +execution_settings: + default: + function_choice_behavior: + type: auto + maximum_auto_invoke_attempts: 5 + filters: + excluded_plugins: + - ChatBot + response_format: + type: json_object + temperature: 0.5 diff --git a/text2sql/rag_with_ai_search_and_text_2_sql.ipynb b/text_2_sql/rag_with_ai_search_and_text_2_sql.ipynb similarity index 97% rename from text2sql/rag_with_ai_search_and_text_2_sql.ipynb rename to text_2_sql/rag_with_ai_search_and_text_2_sql.ipynb index e6fec72..f774448 100644 --- a/text2sql/rag_with_ai_search_and_text_2_sql.ipynb +++ b/text_2_sql/rag_with_ai_search_and_text_2_sql.ipynb @@ -49,7 +49,7 @@ ")\n", "from semantic_kernel.contents.chat_history import ChatHistory\n", "from semantic_kernel.kernel import Kernel\n", - "from plugins.sql_plugin.sql_plugin import SQLPlugin\n", + "from plugins.prompt_based_sql_plugin.prompt_based_sql_plugin import PromptBasedSQLPlugin\n", "from plugins.ai_search_plugin.ai_search_plugin import AISearchPlugin\n", "from semantic_kernel.functions.kernel_arguments import KernelArguments\n", "from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig\n", @@ -125,9 +125,9 @@ "source": [ "chat_service = AzureChatCompletion(\n", " service_id=service_id,\n", - " deployment_name=os.environ[\"OPEN_AI_CONVERSATION_MODEL\"],\n", - " endpoint=os.environ[\"OPEN_AI_ENDPOINT\"],\n", - " api_key=os.environ[\"OPEN_AI_KEY\"],\n", + " deployment_name=os.environ[\"OpenAI__CompletionDeployment\"],\n", + " endpoint=os.environ[\"OpenAI__Endpoint\"],\n", + " api_key=os.environ[\"OpenAI__ApiKey\"],\n", ")\n", "kernel.add_service(chat_service)" ] @@ -163,7 +163,7 @@ ], "source": [ "# Register the SQL Plugin with the Database name to use.\n", - "sql_plugin = SQLPlugin(database=os.environ[\"SQL_DB_NAME\"])\n", + "sql_plugin = PromptBasedSQLPlugin(database=os.environ[\"Text2Sql__DatabaseName\"])\n", "kernel.add_plugin(sql_plugin, \"SQL\")" ] }, diff --git a/text2sql/rag_with_text_2_sql.ipynb b/text_2_sql/rag_with_prompt_based_text_2_sql.ipynb similarity index 99% rename from text2sql/rag_with_text_2_sql.ipynb rename to text_2_sql/rag_with_prompt_based_text_2_sql.ipynb index 9d10c4b..88e26b4 100644 --- a/text2sql/rag_with_text_2_sql.ipynb +++ b/text_2_sql/rag_with_prompt_based_text_2_sql.ipynb @@ -49,7 +49,7 @@ ")\n", "from semantic_kernel.contents.chat_history import ChatHistory\n", "from semantic_kernel.kernel import Kernel\n", - "from plugins.sql_plugin.sql_plugin import SQLPlugin\n", + "from plugins.prompt_based_sql_plugin.prompt_based_sql_plugin import PromptBasedSQLPlugin\n", "from semantic_kernel.functions.kernel_arguments import KernelArguments\n", "from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig\n", "from IPython.display import display, Markdown\n", @@ -124,9 +124,9 @@ "source": [ "chat_service = AzureChatCompletion(\n", " service_id=service_id,\n", - " deployment_name=os.environ[\"OPEN_AI_CONVERSATION_MODEL\"],\n", - " endpoint=os.environ[\"OPEN_AI_ENDPOINT\"],\n", - " api_key=os.environ[\"OPEN_AI_KEY\"],\n", + " deployment_name=os.environ[\"OpenAI__CompletionDeployment\"],\n", + " endpoint=os.environ[\"OpenAI__Endpoint\"],\n", + " api_key=os.environ[\"OpenAI__ApiKey\"],\n", ")\n", "kernel.add_service(chat_service)" ] @@ -162,7 +162,7 @@ ], "source": [ "# Register the SQL Plugin with the Database name to use.\n", - "sql_plugin = SQLPlugin(database=os.environ[\"SQL_DB_NAME\"])\n", + "sql_plugin = PromptBasedSQLPlugin(database=os.environ[\"Text2Sql__DatabaseName\"])\n", "kernel.add_plugin(sql_plugin, \"SQL\")" ] }, diff --git a/text_2_sql/rag_with_vector_based_text_2_sql.ipynb b/text_2_sql/rag_with_vector_based_text_2_sql.ipynb new file mode 100644 index 0000000..7563494 --- /dev/null +++ b/text_2_sql/rag_with_vector_based_text_2_sql.ipynb @@ -0,0 +1,1042 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Text2SQL with Semantic Kernel & Azure OpenAI\n", + "\n", + "This notebook demonstrates how the SQL plugin can be integrated with Semantic Kernel and Azure OpenAI to answer questions from the database based on the schemas provided. \n", + "\n", + "A multi-shot approach is used for SQL generation for more reliable results and reduced token usage. More details can be found in the README.md." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "gather": { + "logged": 1718623217703 + }, + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "import logging\n", + "import os\n", + "import yaml\n", + "import dotenv\n", + "import json\n", + "from semantic_kernel.connectors.ai.open_ai import (\n", + " AzureChatCompletion,\n", + ")\n", + "from semantic_kernel.contents.chat_history import ChatHistory\n", + "from semantic_kernel.kernel import Kernel\n", + "from plugins.vector_based_sql_plugin.vector_based_sql_plugin import VectorBasedSQLPlugin\n", + "from semantic_kernel.functions.kernel_arguments import KernelArguments\n", + "from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig\n", + "from IPython.display import display, Markdown\n", + "\n", + "logging.basicConfig(level=logging.INFO)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Kernel Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "dotenv.load_dotenv()\n", + "kernel = Kernel()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up GPT connections" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "gather": { + "logged": 1718623218006 + }, + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "service_id = \"chat\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "gather": { + "logged": 1718623218267 + }, + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "chat_service = AzureChatCompletion(\n", + " service_id=service_id,\n", + " deployment_name=os.environ[\"OpenAI__CompletionDeployment\"],\n", + " endpoint=os.environ[\"OpenAI__Endpoint\"],\n", + " api_key=os.environ[\"OpenAI__ApiKey\"],\n", + ")\n", + "kernel.add_service(chat_service)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "gather": { + "logged": 1718623218614 + }, + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "KernelPlugin(name='SQL', description=None, functions={'GetEntitySchema': KernelFunctionFromMethod(metadata=KernelFunctionMetadata(name='GetEntitySchema', plugin_name='SQL', description='Gets the schema of a view or table in the SQL Database by selecting the most relevant entity based on the search term. Several entities may be returned.', parameters=[KernelParameterMetadata(name='text', description='The text to run a semantic search against. Relevant entities will be returned.', default_value=None, type_='str', is_required=True, type_object=, schema_data={'type': 'string', 'description': 'The text to run a semantic search against. Relevant entities will be returned.'}, function_schema_include=True)], is_prompt=False, is_asynchronous=True, return_parameter=KernelParameterMetadata(name='return', description='', default_value=None, type_='str', is_required=True, type_object=, schema_data={'type': 'string'}, function_schema_include=True), additional_properties={}), invocation_duration_histogram=, streaming_duration_histogram=, method=>, stream_method=None), 'RunSQLQuery': KernelFunctionFromMethod(metadata=KernelFunctionMetadata(name='RunSQLQuery', plugin_name='SQL', description='Runs an SQL query against the SQL Database to extract information.', parameters=[KernelParameterMetadata(name='sql_query', description='The SQL query to run against the DB', default_value=None, type_='str', is_required=True, type_object=, schema_data={'type': 'string', 'description': 'The SQL query to run against the DB'}, function_schema_include=True)], is_prompt=False, is_asynchronous=True, return_parameter=KernelParameterMetadata(name='return', description='', default_value=None, type_='str', is_required=True, type_object=, schema_data={'type': 'string'}, function_schema_include=True), additional_properties={}), invocation_duration_histogram=, streaming_duration_histogram=, method=>, stream_method=None)})" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Register the SQL Plugin with the Database name to use.\n", + "sql_plugin = VectorBasedSQLPlugin(database=os.environ[\"Text2Sql__DatabaseName\"])\n", + "kernel.add_plugin(sql_plugin, \"SQL\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "## Prompt Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Load prompt and execution settings from the file\n", + "with open(\"./prompt.yaml\", \"r\") as file:\n", + " data = yaml.safe_load(file.read())\n", + " prompt_template_config = PromptTemplateConfig(**data)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "chat_function = kernel.add_function(\n", + " prompt_template_config=prompt_template_config,\n", + " plugin_name=\"ChatBot\",\n", + " function_name=\"Chat\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ChatBot setup" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "history = ChatHistory()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "async def ask_question(question: str, chat_history: ChatHistory) -> str:\n", + " \"\"\"Asks a question to the chatbot and returns the answer.\n", + " \n", + " Args:\n", + " question (str): The question to ask the chatbot.\n", + " chat_history (ChatHistory): The chat history object.\n", + " \n", + " Returns:\n", + " str: The answer from the chatbot.\n", + " \"\"\"\n", + "\n", + " # Create important information prompt that contains the SQL database information.\n", + " engine_specific_rules = \"Use TOP X to limit the number of rows returned instead of LIMIT X. NEVER USE LIMIT X as it produces a syntax error.\"\n", + " important_information_prompt = f\"\"\"\n", + " [SQL DATABASE INFORMATION]\n", + " {sql_plugin.system_prompt(engine_specific_rules=engine_specific_rules)}\n", + " [END SQL DATABASE INFORMATION]\n", + " \"\"\"\n", + "\n", + " arguments = KernelArguments()\n", + " arguments[\"chat_history\"] = chat_history\n", + " arguments[\"important_information\"] = important_information_prompt\n", + " arguments[\"user_input\"] = question\n", + "\n", + " logging.info(\"Question: %s\", question)\n", + "\n", + " answer = await kernel.invoke(\n", + " function_name=\"Chat\",\n", + " plugin_name=\"ChatBot\",\n", + " arguments=arguments,\n", + " chat_history=chat_history,\n", + " )\n", + "\n", + " logging.info(\"Answer: %s\", answer)\n", + "\n", + " # Log the question and answer to the chat history.\n", + " chat_history.add_user_message(question)\n", + " chat_history.add_message({\"role\": \"assistant\", \"message\": answer})\n", + "\n", + " json_answer = json.loads(str(answer))\n", + "\n", + " display(Markdown(json_answer[\"answer\"]))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Question: What are the different product categories we have?\n", + "INFO:semantic_kernel.functions.kernel_function:Function ChatBot-Chat invoking.\n", + "INFO:semantic_kernel.contents.chat_history:Could not parse prompt \n", + "As a senior analyst, your primary responsibility is to provide precise and thorough answers to the user's queries. Utilize all the provided functions to craft your responses. You must deliver detailed and accurate final answers with clear explanations and actionable insights.\n", + "\n", + "Always use the provided functions to obtain key information in order to answer the question.\n", + "If you are asked to use always use a function, you must use that function to compliment the answer.\n", + "Always use multiple functions to formulate the answer.\n", + "Always execute multiple functions in parallel to compliment the results.\n", + "\n", + "The response to the user must meet the requirements in RESPONSE OUTPUT REQUIREMENTS.\n", + "IMPORTANT INFORMATION contains useful information that you can use to aid your knowledge.\n", + "CHAT HISTORY contains the previous question and answer pairs in the conversation in JSON format. Do not use this information to answer the question, but to provide context on what was asked previously.\n", + "\n", + "[IMPORTANT INFORMATION]\n", + "\n", + "\n", + " [SQL DATABASE INFORMATION]\n", + " Use the 'GetEntitySchema()' function to search for the most relevant schemas for the data that you wish to obtain. Use the 'RunSQLQuery()' function to run the SQL query against the database.\n", + "\n", + " Output corresponding text values in the answer for columns where there is an ID. For example, if the column is 'ProductID', output the corresponding 'ProductModel' in the response. Do not include the ID in the response.\n", + " If a user is asking for a comparison, always compare the relevant values in the database.\n", + "\n", + " The target database engine is Microsoft TSQL Server, SQL queries must be able compatible to run on Microsoft TSQL Server. \n", + " The following Microsoft TSQL Server Syntax rules must be adhered to.\n", + " Use TOP X to limit the number of rows returned instead of LIMIT X. NEVER USE LIMIT X as it produces a syntax error.\n", + " Always generate the SQL query based on the GetEntitySchema() function output, do not use the chat history data to generate the SQL query.\n", + " Only use the column names obtained from GetEntitySchema() when constructing a SQL query, do not make up column names.\n", + " You must only provide SELECT SQL queries.\n", + " For a given entity, use the 'SelectFromEntity' property returned from 'GetEntitySchema()' function in the SELECT FROM part of the SQL query. If the property is {'SelectFromEntity': 'test_schema.test_table'}, the select statement will be formulated from 'SELECT <VALUES> FROM test_schema.test_table WHERE <CONDITION>.\n", + "\n", + " If you don't know how the value is formatted in a column, run a query against the column to get the unique values that might match your query.\n", + " Some columns returned from 'GetEntitySchema()' may have the properties 'AllowedValues' or 'SampleValues'. Use these values to determine the possible values that can be used in the SQL query.\n", + "\n", + " The source title to cite is the 'EntityName' property. The source reference is the SQL query used. The source chunk is the result of the SQL query used to answer the user query in Markdown table format. e.g. { 'title': "vProductAndDescription", 'chunk': '| ProductID | Name | ProductModel | Culture | Description |\\n|-----------|-------------------|--------------|---------|----------------------------------|\\n| 101 | Mountain Bike | MT-100 | en | A durable bike for mountain use. |\\n| 102 | Road Bike | RB-200 | en | Lightweight bike for road use. |\\n| 103 | Hybrid Bike | HB-300 | fr | V\u00e9lo hybride pour usage mixte. |\\n', 'reference': 'SELECT ProductID, Name, ProductModel, Culture, Description FROM vProductAndDescription WHERE Culture = "en";' }\n", + " [END SQL DATABASE INFORMATION]\n", + " \n", + "\n", + "[END IMPORTANT INFORMATION]\n", + "\n", + "[RESPONSE OUTPUT REQUIREMENTS]\n", + "\n", + " The answer MUST be returned in JSON format as { \"answer\": \"\", \"sources\": [ {\"title\": , \"chunk\": , \"reference\": \"\"}, {\"title\": , \"chunk\": , \"reference\": \"\"} ] }.\n", + "\n", + " The 'answer' property MUST meet the requirements in the ANSWER PROPERTY REQUIREMENTS.\n", + " The 'sources' property MUST meet the requirements in the SOURCES PROPERTY REQUIREMENTS.\n", + "\n", + " Do NOT return anything outside of the provided JSON property. Ensure that this is valid JSON returned.\n", + "\n", + " Never return an empty response or null value. Always answer the question.\n", + "\n", + " [ANSWER PROPERTY REQUIREMENTS]\n", + " 1. Language and Tone:\n", + " Use only British English throughout the response.\n", + " Employ a business-friendly language that is professional and easy to understand.\n", + "\n", + " 2. Content Restrictions:\n", + " Do not use any profanity, offensive language, hate speech, or code in the response.\n", + " If you encounter any such content, handle it gracefully by omitting or rephrasing it appropriately.\n", + "\n", + " 3. Information Sources:\n", + " Use only information from the provided functions and specified important information.\n", + " Do not use any external sources or the chat history for constructing the response.\n", + " In case of conflicting information, prioritize data from the SQL Database as the primary source of truth.\n", + "\n", + " 4. Calculations:\n", + " For any required calculations, use only the values provided in the context.\n", + " Provide a brief, clear explanation of the calculations beneath the results.\n", + "\n", + " 5. Response Structure:\n", + " Ensure the response is direct, easy to understand, and well-structured.\n", + " Format the response using Markdown for clarity and readability.\n", + " Use bold sub-headings for clarity where needed. Only use Markdown headings Level 3 (###) and Level 4 (####).\n", + " Use bullet points or numbered lists when appropriate.\n", + " Do not vary the font size within the same sentence.\n", + "\n", + " 6. Citations:\n", + " All factual information used in the answer must be cited with numbered references. For example, [1] should be used to refer to the first source.\n", + " Each citation in the answer must correspond to a single entry in the 'sources' object.\n", + " The same citation and corresponding context chunk may be used multiple times if needed.\n", + " Place the numbered citation at the end of each relevant sentence that uses information from the sources.\n", + " Ensure that each source listed in the 'sources' property is cited at least once in the answer.\n", + " Do not provide a list of definitions from the business glossary; use such information only to enhance the answer contextually.\n", + "\n", + " 7. Citations Format:\n", + " Citations should be embedded within the text, not as a separate list at the end of the 'answer' property.\n", + " [END ANSWER PROPERTY REQUIREMENTS]\n", + "\n", + " [SOURCES PROPERTY REQUIREMENTS]\n", + " 1. Reference Inclusion:\n", + " Include all corresponding references for all cited content in the 'answer' property.\n", + " Place the references in the 'sources' property.\n", + "\n", + " 2. Source Format:\n", + " Each entry in the 'sources' property must be formatted as: {\"title\": \"\", \"chunk\": \"\", \"reference\": \"\"}\n", + " For example, a complete response with two citations would be formatted as: { \"answer\": \"\", \"sources\": [ {\"title\": , \"chunk\": , \"reference\": \"\"}, {\"title\": , \"chunk\": , \"reference\": \"\"} ] }\n", + "\n", + " 3. Source Chunk:\n", + " The 'chunk' property should contain a concise, unedited snippet of the relevant context that supports the answer.\n", + "\n", + " 4. Mandatory References:\n", + " Ensure that every citation in the 'answer' has a corresponding entry in the 'sources' property.\n", + " Every entry in the 'sources' property must be cited at least once in the answer.\n", + " [END SOURCES PROPERTY REQUIREMENTS]\n", + "\n", + "[END RESPONSE OUTPUT REQUIREMENTS]\n", + "\n", + "\n", + "What are the different product categories we have? as xml, treating as text, error was: not well-formed (invalid token): line 41, column 78\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=51, prompt_tokens=1815, total_tokens=1866)\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 2 tool calls in parallel.\n", + "INFO:semantic_kernel.kernel:Calling SQL-GetEntitySchema function with args: {\"text\": \"Product Category\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema invoking.\n", + "INFO:semantic_kernel.kernel:Calling SQL-GetEntitySchema function with args: {\"text\": \"Product\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema invoking.\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-03-15-preview \"HTTP/1.1 200 OK\"\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'https://open-ai-vector-db.search.windows.net/indexes('text-2-sql-index')/docs/search.post.search?api-version=REDACTED'\n", + "Request method: 'POST'\n", + "Request headers:\n", + " 'Content-Type': 'application/json'\n", + " 'Content-Length': '34765'\n", + " 'api-key': 'REDACTED'\n", + " 'Accept': 'application/json;odata.metadata=none'\n", + " 'x-ms-client-request-id': '435b3768-706a-11ef-ae73-0242ac110002'\n", + " 'User-Agent': 'azsdk-python-search-documents/11.6.0b4 Python/3.12.3 (Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.36)'\n", + "A body is sent with the request\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-03-15-preview \"HTTP/1.1 200 OK\"\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'https://open-ai-vector-db.search.windows.net/indexes('text-2-sql-index')/docs/search.post.search?api-version=REDACTED'\n", + "Request method: 'POST'\n", + "Request headers:\n", + " 'Content-Type': 'application/json'\n", + " 'Content-Length': '34721'\n", + " 'api-key': 'REDACTED'\n", + " 'Accept': 'application/json;odata.metadata=none'\n", + " 'x-ms-client-request-id': '4360c9a8-706a-11ef-ae73-0242ac110002'\n", + " 'User-Agent': 'azsdk-python-search-documents/11.6.0b4 Python/3.12.3 (Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.36)'\n", + "A body is sent with the request\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 200\n", + "Response headers:\n", + " 'Transfer-Encoding': 'chunked'\n", + " 'Content-Type': 'application/json; odata.metadata=none; odata.streaming=true; charset=utf-8'\n", + " 'Content-Encoding': 'REDACTED'\n", + " 'Vary': 'REDACTED'\n", + " 'Server': 'Microsoft-IIS/10.0'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Preference-Applied': 'REDACTED'\n", + " 'OData-Version': 'REDACTED'\n", + " 'request-id': '435b3768-706a-11ef-ae73-0242ac110002'\n", + " 'elapsed-time': 'REDACTED'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Date': 'Wed, 11 Sep 2024 18:18:39 GMT'\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.590935s\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 200\n", + "Response headers:\n", + " 'Transfer-Encoding': 'chunked'\n", + " 'Content-Type': 'application/json; odata.metadata=none; odata.streaming=true; charset=utf-8'\n", + " 'Content-Encoding': 'REDACTED'\n", + " 'Vary': 'REDACTED'\n", + " 'Server': 'Microsoft-IIS/10.0'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Preference-Applied': 'REDACTED'\n", + " 'OData-Version': 'REDACTED'\n", + " 'request-id': '4360c9a8-706a-11ef-ae73-0242ac110002'\n", + " 'elapsed-time': 'REDACTED'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Date': 'Wed, 11 Sep 2024 18:18:39 GMT'\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.566008s\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 429 Too Many Requests\"\n", + "INFO:openai._base_client:Retrying request to /chat/completions in 1.000000 seconds\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=34, prompt_tokens=5818, total_tokens=5852)\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 1 tool calls in parallel.\n", + "INFO:semantic_kernel.kernel:Calling SQL-RunSQLQuery function with args: {\"sql_query\":\"SELECT ParentProductCategoryName, ProductCategoryName FROM SalesLT.vGetAllCategories;\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-RunSQLQuery invoking.\n", + "INFO:root:Executing SQL Query\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-RunSQLQuery succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.369210s\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=636, prompt_tokens=6556, total_tokens=7192)\n", + "INFO:semantic_kernel.functions.kernel_function:Function ChatBot-Chat succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 9.696862s\n", + "INFO:root:Answer: {\n", + " \"answer\": \"Our product categories are organised into several parent categories, each containing multiple subcategories. Here is a detailed list of the different product categories we have:\\n\\n### Accessories\\n- Bike Racks\\n- Bike Stands\\n- Bottles and Cages\\n- Cleaners\\n- Fenders\\n- Helmets\\n- Hydration Packs\\n- Lights\\n- Locks\\n- Panniers\\n- Pumps\\n- Tires and Tubes\\n\\n### Clothing\\n- Bib-Shorts\\n- Caps\\n- Gloves\\n- Jerseys\\n- Shorts\\n- Socks\\n- Tights\\n- Vests\\n\\n### Components\\n- Handlebars\\n- Bottom Brackets\\n- Brakes\\n- Chains\\n- Cranksets\\n- Derailleurs\\n- Forks\\n- Headsets\\n- Mountain Frames\\n- Pedals\\n- Road Frames\\n- Saddles\\n- Touring Frames\\n- Wheels\\n\\n### Bikes\\n- Mountain Bikes\\n- Road Bikes\\n- Touring Bikes\\n\\nThese categories help in better organising and managing our product inventory, making it easier for customers to find what they need [1].\",\n", + " \"sources\": [\n", + " {\n", + " \"title\": \"Get All Categories\",\n", + " \"chunk\": \"| ParentProductCategoryName | ProductCategoryName |\\n|---------------------------|---------------------|\\n| Accessories | Bike Racks |\\n| Accessories | Bike Stands |\\n| Accessories | Bottles and Cages |\\n| Accessories | Cleaners |\\n| Accessories | Fenders |\\n| Accessories | Helmets |\\n| Accessories | Hydration Packs |\\n| Accessories | Lights |\\n| Accessories | Locks |\\n| Accessories | Panniers |\\n| Accessories | Pumps |\\n| Accessories | Tires and Tubes |\\n| Clothing | Bib-Shorts |\\n| Clothing | Caps |\\n| Clothing | Gloves |\\n| Clothing | Jerseys |\\n| Clothing | Shorts |\\n| Clothing | Socks |\\n| Clothing | Tights |\\n| Clothing | Vests |\\n| Components | Handlebars |\\n| Components | Bottom Brackets |\\n| Components | Brakes |\\n| Components | Chains |\\n| Components | Cranksets |\\n| Components | Derailleurs |\\n| Components | Forks |\\n| Components | Headsets |\\n| Components | Mountain Frames |\\n| Components | Pedals |\\n| Components | Road Frames |\\n| Components | Saddles |\\n| Components | Touring Frames |\\n| Components | Wheels |\\n| Bikes | Mountain Bikes |\\n| Bikes | Road Bikes |\\n| Bikes | Touring Bikes |\\n\",\n", + " \"reference\": \"SELECT ParentProductCategoryName, ProductCategoryName FROM SalesLT.vGetAllCategories;\"\n", + " }\n", + " ]\n", + "}\n" + ] + }, + { + "data": { + "text/markdown": [ + "Our product categories are organised into several parent categories, each containing multiple subcategories. Here is a detailed list of the different product categories we have:\n", + "\n", + "### Accessories\n", + "- Bike Racks\n", + "- Bike Stands\n", + "- Bottles and Cages\n", + "- Cleaners\n", + "- Fenders\n", + "- Helmets\n", + "- Hydration Packs\n", + "- Lights\n", + "- Locks\n", + "- Panniers\n", + "- Pumps\n", + "- Tires and Tubes\n", + "\n", + "### Clothing\n", + "- Bib-Shorts\n", + "- Caps\n", + "- Gloves\n", + "- Jerseys\n", + "- Shorts\n", + "- Socks\n", + "- Tights\n", + "- Vests\n", + "\n", + "### Components\n", + "- Handlebars\n", + "- Bottom Brackets\n", + "- Brakes\n", + "- Chains\n", + "- Cranksets\n", + "- Derailleurs\n", + "- Forks\n", + "- Headsets\n", + "- Mountain Frames\n", + "- Pedals\n", + "- Road Frames\n", + "- Saddles\n", + "- Touring Frames\n", + "- Wheels\n", + "\n", + "### Bikes\n", + "- Mountain Bikes\n", + "- Road Bikes\n", + "- Touring Bikes\n", + "\n", + "These categories help in better organising and managing our product inventory, making it easier for customers to find what they need [1]." + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "await ask_question(\"What are the different product categories we have?\", history)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Question: What is the top performing product by quantity of units sold?\n", + "INFO:semantic_kernel.functions.kernel_function:Function ChatBot-Chat invoking.\n", + "INFO:semantic_kernel.contents.chat_history:Could not parse prompt \n", + "As a senior analyst, your primary responsibility is to provide precise and thorough answers to the user's queries. Utilize all the provided functions to craft your responses. You must deliver detailed and accurate final answers with clear explanations and actionable insights.\n", + "\n", + "Always use the provided functions to obtain key information in order to answer the question.\n", + "If you are asked to use always use a function, you must use that function to compliment the answer.\n", + "Always use multiple functions to formulate the answer.\n", + "Always execute multiple functions in parallel to compliment the results.\n", + "\n", + "The response to the user must meet the requirements in RESPONSE OUTPUT REQUIREMENTS.\n", + "IMPORTANT INFORMATION contains useful information that you can use to aid your knowledge.\n", + "CHAT HISTORY contains the previous question and answer pairs in the conversation in JSON format. Do not use this information to answer the question, but to provide context on what was asked previously.\n", + "\n", + "[IMPORTANT INFORMATION]\n", + "\n", + "\n", + " [SQL DATABASE INFORMATION]\n", + " Use the 'GetEntitySchema()' function to search for the most relevant schemas for the data that you wish to obtain. Use the 'RunSQLQuery()' function to run the SQL query against the database.\n", + "\n", + " Output corresponding text values in the answer for columns where there is an ID. For example, if the column is 'ProductID', output the corresponding 'ProductModel' in the response. Do not include the ID in the response.\n", + " If a user is asking for a comparison, always compare the relevant values in the database.\n", + "\n", + " The target database engine is Microsoft TSQL Server, SQL queries must be able compatible to run on Microsoft TSQL Server. \n", + " The following Microsoft TSQL Server Syntax rules must be adhered to.\n", + " Use TOP X to limit the number of rows returned instead of LIMIT X. NEVER USE LIMIT X as it produces a syntax error.\n", + " Always generate the SQL query based on the GetEntitySchema() function output, do not use the chat history data to generate the SQL query.\n", + " Only use the column names obtained from GetEntitySchema() when constructing a SQL query, do not make up column names.\n", + " You must only provide SELECT SQL queries.\n", + " For a given entity, use the 'SelectFromEntity' property returned from 'GetEntitySchema()' function in the SELECT FROM part of the SQL query. If the property is {'SelectFromEntity': 'test_schema.test_table'}, the select statement will be formulated from 'SELECT <VALUES> FROM test_schema.test_table WHERE <CONDITION>.\n", + "\n", + " If you don't know how the value is formatted in a column, run a query against the column to get the unique values that might match your query.\n", + " Some columns returned from 'GetEntitySchema()' may have the properties 'AllowedValues' or 'SampleValues'. Use these values to determine the possible values that can be used in the SQL query.\n", + "\n", + " The source title to cite is the 'EntityName' property. The source reference is the SQL query used. The source chunk is the result of the SQL query used to answer the user query in Markdown table format. e.g. { 'title': "vProductAndDescription", 'chunk': '| ProductID | Name | ProductModel | Culture | Description |\\n|-----------|-------------------|--------------|---------|----------------------------------|\\n| 101 | Mountain Bike | MT-100 | en | A durable bike for mountain use. |\\n| 102 | Road Bike | RB-200 | en | Lightweight bike for road use. |\\n| 103 | Hybrid Bike | HB-300 | fr | V\u00e9lo hybride pour usage mixte. |\\n', 'reference': 'SELECT ProductID, Name, ProductModel, Culture, Description FROM vProductAndDescription WHERE Culture = "en";' }\n", + " [END SQL DATABASE INFORMATION]\n", + " \n", + "\n", + "[END IMPORTANT INFORMATION]\n", + "\n", + "[RESPONSE OUTPUT REQUIREMENTS]\n", + "\n", + " The answer MUST be returned in JSON format as { \"answer\": \"\", \"sources\": [ {\"title\": , \"chunk\": , \"reference\": \"\"}, {\"title\": , \"chunk\": , \"reference\": \"\"} ] }.\n", + "\n", + " The 'answer' property MUST meet the requirements in the ANSWER PROPERTY REQUIREMENTS.\n", + " The 'sources' property MUST meet the requirements in the SOURCES PROPERTY REQUIREMENTS.\n", + "\n", + " Do NOT return anything outside of the provided JSON property. Ensure that this is valid JSON returned.\n", + "\n", + " Never return an empty response or null value. Always answer the question.\n", + "\n", + " [ANSWER PROPERTY REQUIREMENTS]\n", + " 1. Language and Tone:\n", + " Use only British English throughout the response.\n", + " Employ a business-friendly language that is professional and easy to understand.\n", + "\n", + " 2. Content Restrictions:\n", + " Do not use any profanity, offensive language, hate speech, or code in the response.\n", + " If you encounter any such content, handle it gracefully by omitting or rephrasing it appropriately.\n", + "\n", + " 3. Information Sources:\n", + " Use only information from the provided functions and specified important information.\n", + " Do not use any external sources or the chat history for constructing the response.\n", + " In case of conflicting information, prioritize data from the SQL Database as the primary source of truth.\n", + "\n", + " 4. Calculations:\n", + " For any required calculations, use only the values provided in the context.\n", + " Provide a brief, clear explanation of the calculations beneath the results.\n", + "\n", + " 5. Response Structure:\n", + " Ensure the response is direct, easy to understand, and well-structured.\n", + " Format the response using Markdown for clarity and readability.\n", + " Use bold sub-headings for clarity where needed. Only use Markdown headings Level 3 (###) and Level 4 (####).\n", + " Use bullet points or numbered lists when appropriate.\n", + " Do not vary the font size within the same sentence.\n", + "\n", + " 6. Citations:\n", + " All factual information used in the answer must be cited with numbered references. For example, [1] should be used to refer to the first source.\n", + " Each citation in the answer must correspond to a single entry in the 'sources' object.\n", + " The same citation and corresponding context chunk may be used multiple times if needed.\n", + " Place the numbered citation at the end of each relevant sentence that uses information from the sources.\n", + " Ensure that each source listed in the 'sources' property is cited at least once in the answer.\n", + " Do not provide a list of definitions from the business glossary; use such information only to enhance the answer contextually.\n", + "\n", + " 7. Citations Format:\n", + " Citations should be embedded within the text, not as a separate list at the end of the 'answer' property.\n", + " [END ANSWER PROPERTY REQUIREMENTS]\n", + "\n", + " [SOURCES PROPERTY REQUIREMENTS]\n", + " 1. Reference Inclusion:\n", + " Include all corresponding references for all cited content in the 'answer' property.\n", + " Place the references in the 'sources' property.\n", + "\n", + " 2. Source Format:\n", + " Each entry in the 'sources' property must be formatted as: {\"title\": \"\", \"chunk\": \"\", \"reference\": \"\"}\n", + " For example, a complete response with two citations would be formatted as: { \"answer\": \"\", \"sources\": [ {\"title\": , \"chunk\": , \"reference\": \"\"}, {\"title\": , \"chunk\": , \"reference\": \"\"} ] }\n", + "\n", + " 3. Source Chunk:\n", + " The 'chunk' property should contain a concise, unedited snippet of the relevant context that supports the answer.\n", + "\n", + " 4. Mandatory References:\n", + " Ensure that every citation in the 'answer' has a corresponding entry in the 'sources' property.\n", + " Every entry in the 'sources' property must be cited at least once in the answer.\n", + " [END SOURCES PROPERTY REQUIREMENTS]\n", + "\n", + "[END RESPONSE OUTPUT REQUIREMENTS]\n", + "\n", + "What are the different product categories we have?\n", + "What is the top performing product by quantity of units sold? as xml, treating as text, error was: not well-formed (invalid token): line 41, column 78\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=51, prompt_tokens=1847, total_tokens=1898)\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 2 tool calls in parallel.\n", + "INFO:semantic_kernel.kernel:Calling SQL-GetEntitySchema function with args: {\"text\": \"product sales\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema invoking.\n", + "INFO:semantic_kernel.kernel:Calling SQL-GetEntitySchema function with args: {\"text\": \"products\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema invoking.\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-03-15-preview \"HTTP/1.1 200 OK\"\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'https://open-ai-vector-db.search.windows.net/indexes('text-2-sql-index')/docs/search.post.search?api-version=REDACTED'\n", + "Request method: 'POST'\n", + "Request headers:\n", + " 'Content-Type': 'application/json'\n", + " 'Content-Length': '34711'\n", + " 'api-key': 'REDACTED'\n", + " 'Accept': 'application/json;odata.metadata=none'\n", + " 'x-ms-client-request-id': '48f29234-706a-11ef-ae73-0242ac110002'\n", + " 'User-Agent': 'azsdk-python-search-documents/11.6.0b4 Python/3.12.3 (Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.36)'\n", + "A body is sent with the request\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-03-15-preview \"HTTP/1.1 200 OK\"\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'https://open-ai-vector-db.search.windows.net/indexes('text-2-sql-index')/docs/search.post.search?api-version=REDACTED'\n", + "Request method: 'POST'\n", + "Request headers:\n", + " 'Content-Type': 'application/json'\n", + " 'Content-Length': '34720'\n", + " 'api-key': 'REDACTED'\n", + " 'Accept': 'application/json;odata.metadata=none'\n", + " 'x-ms-client-request-id': '48f7c98e-706a-11ef-ae73-0242ac110002'\n", + " 'User-Agent': 'azsdk-python-search-documents/11.6.0b4 Python/3.12.3 (Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.36)'\n", + "A body is sent with the request\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 200\n", + "Response headers:\n", + " 'Transfer-Encoding': 'chunked'\n", + " 'Content-Type': 'application/json; odata.metadata=none; odata.streaming=true; charset=utf-8'\n", + " 'Content-Encoding': 'REDACTED'\n", + " 'Vary': 'REDACTED'\n", + " 'Server': 'Microsoft-IIS/10.0'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Preference-Applied': 'REDACTED'\n", + " 'OData-Version': 'REDACTED'\n", + " 'request-id': '48f29234-706a-11ef-ae73-0242ac110002'\n", + " 'elapsed-time': 'REDACTED'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Date': 'Wed, 11 Sep 2024 18:18:48 GMT'\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.482475s\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 200\n", + "Response headers:\n", + " 'Transfer-Encoding': 'chunked'\n", + " 'Content-Type': 'application/json; odata.metadata=none; odata.streaming=true; charset=utf-8'\n", + " 'Content-Encoding': 'REDACTED'\n", + " 'Vary': 'REDACTED'\n", + " 'Server': 'Microsoft-IIS/10.0'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Preference-Applied': 'REDACTED'\n", + " 'OData-Version': 'REDACTED'\n", + " 'request-id': '48f7c98e-706a-11ef-ae73-0242ac110002'\n", + " 'elapsed-time': 'REDACTED'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Date': 'Wed, 11 Sep 2024 18:18:48 GMT'\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.533455s\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=65, prompt_tokens=5827, total_tokens=5892)\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 1 tool calls in parallel.\n", + "INFO:semantic_kernel.kernel:Calling SQL-RunSQLQuery function with args: {\"sql_query\":\"SELECT TOP 1 p.Name, SUM(d.OrderQty) AS TotalUnitsSold FROM SalesLT.SalesOrderDetail d JOIN SalesLT.Product p ON d.ProductID = p.ProductID GROUP BY p.Name ORDER BY TotalUnitsSold DESC;\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-RunSQLQuery invoking.\n", + "INFO:root:Executing SQL Query\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-RunSQLQuery succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.301421s\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=142, prompt_tokens=5921, total_tokens=6063)\n", + "INFO:semantic_kernel.functions.kernel_function:Function ChatBot-Chat succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 3.936345s\n", + "INFO:root:Answer: { \n", + " \"answer\": \"The top performing product by quantity of units sold is the 'Classic Vest, S' with a total of 87 units sold [1].\",\n", + " \"sources\": [\n", + " {\n", + " \"title\": \"Sales Order Detail\",\n", + " \"chunk\": \"| Name | TotalUnitsSold |\\n|---------------|----------------|\\n| Classic Vest, S | 87 |\\n\",\n", + " \"reference\": \"SELECT TOP 1 p.Name, SUM(d.OrderQty) AS TotalUnitsSold FROM SalesLT.SalesOrderDetail d JOIN SalesLT.Product p ON d.ProductID = p.ProductID GROUP BY p.Name ORDER BY TotalUnitsSold DESC;\"\n", + " }\n", + " ]\n", + "}\n" + ] + }, + { + "data": { + "text/markdown": [ + "The top performing product by quantity of units sold is the 'Classic Vest, S' with a total of 87 units sold [1]." + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "await ask_question(\"What is the top performing product by quantity of units sold?\", history)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Question: Which country did we sell the most to in June 2008?\n", + "INFO:semantic_kernel.functions.kernel_function:Function ChatBot-Chat invoking.\n", + "INFO:semantic_kernel.contents.chat_history:Could not parse prompt \n", + "As a senior analyst, your primary responsibility is to provide precise and thorough answers to the user's queries. Utilize all the provided functions to craft your responses. You must deliver detailed and accurate final answers with clear explanations and actionable insights.\n", + "\n", + "Always use the provided functions to obtain key information in order to answer the question.\n", + "If you are asked to use always use a function, you must use that function to compliment the answer.\n", + "Always use multiple functions to formulate the answer.\n", + "Always execute multiple functions in parallel to compliment the results.\n", + "\n", + "The response to the user must meet the requirements in RESPONSE OUTPUT REQUIREMENTS.\n", + "IMPORTANT INFORMATION contains useful information that you can use to aid your knowledge.\n", + "CHAT HISTORY contains the previous question and answer pairs in the conversation in JSON format. Do not use this information to answer the question, but to provide context on what was asked previously.\n", + "\n", + "[IMPORTANT INFORMATION]\n", + "\n", + "\n", + " [SQL DATABASE INFORMATION]\n", + " Use the 'GetEntitySchema()' function to search for the most relevant schemas for the data that you wish to obtain. Use the 'RunSQLQuery()' function to run the SQL query against the database.\n", + "\n", + " Output corresponding text values in the answer for columns where there is an ID. For example, if the column is 'ProductID', output the corresponding 'ProductModel' in the response. Do not include the ID in the response.\n", + " If a user is asking for a comparison, always compare the relevant values in the database.\n", + "\n", + " The target database engine is Microsoft TSQL Server, SQL queries must be able compatible to run on Microsoft TSQL Server. \n", + " The following Microsoft TSQL Server Syntax rules must be adhered to.\n", + " Use TOP X to limit the number of rows returned instead of LIMIT X. NEVER USE LIMIT X as it produces a syntax error.\n", + " Always generate the SQL query based on the GetEntitySchema() function output, do not use the chat history data to generate the SQL query.\n", + " Only use the column names obtained from GetEntitySchema() when constructing a SQL query, do not make up column names.\n", + " You must only provide SELECT SQL queries.\n", + " For a given entity, use the 'SelectFromEntity' property returned from 'GetEntitySchema()' function in the SELECT FROM part of the SQL query. If the property is {'SelectFromEntity': 'test_schema.test_table'}, the select statement will be formulated from 'SELECT <VALUES> FROM test_schema.test_table WHERE <CONDITION>.\n", + "\n", + " If you don't know how the value is formatted in a column, run a query against the column to get the unique values that might match your query.\n", + " Some columns returned from 'GetEntitySchema()' may have the properties 'AllowedValues' or 'SampleValues'. Use these values to determine the possible values that can be used in the SQL query.\n", + "\n", + " The source title to cite is the 'EntityName' property. The source reference is the SQL query used. The source chunk is the result of the SQL query used to answer the user query in Markdown table format. e.g. { 'title': "vProductAndDescription", 'chunk': '| ProductID | Name | ProductModel | Culture | Description |\\n|-----------|-------------------|--------------|---------|----------------------------------|\\n| 101 | Mountain Bike | MT-100 | en | A durable bike for mountain use. |\\n| 102 | Road Bike | RB-200 | en | Lightweight bike for road use. |\\n| 103 | Hybrid Bike | HB-300 | fr | V\u00e9lo hybride pour usage mixte. |\\n', 'reference': 'SELECT ProductID, Name, ProductModel, Culture, Description FROM vProductAndDescription WHERE Culture = "en";' }\n", + " [END SQL DATABASE INFORMATION]\n", + " \n", + "\n", + "[END IMPORTANT INFORMATION]\n", + "\n", + "[RESPONSE OUTPUT REQUIREMENTS]\n", + "\n", + " The answer MUST be returned in JSON format as { \"answer\": \"\", \"sources\": [ {\"title\": , \"chunk\": , \"reference\": \"\"}, {\"title\": , \"chunk\": , \"reference\": \"\"} ] }.\n", + "\n", + " The 'answer' property MUST meet the requirements in the ANSWER PROPERTY REQUIREMENTS.\n", + " The 'sources' property MUST meet the requirements in the SOURCES PROPERTY REQUIREMENTS.\n", + "\n", + " Do NOT return anything outside of the provided JSON property. Ensure that this is valid JSON returned.\n", + "\n", + " Never return an empty response or null value. Always answer the question.\n", + "\n", + " [ANSWER PROPERTY REQUIREMENTS]\n", + " 1. Language and Tone:\n", + " Use only British English throughout the response.\n", + " Employ a business-friendly language that is professional and easy to understand.\n", + "\n", + " 2. Content Restrictions:\n", + " Do not use any profanity, offensive language, hate speech, or code in the response.\n", + " If you encounter any such content, handle it gracefully by omitting or rephrasing it appropriately.\n", + "\n", + " 3. Information Sources:\n", + " Use only information from the provided functions and specified important information.\n", + " Do not use any external sources or the chat history for constructing the response.\n", + " In case of conflicting information, prioritize data from the SQL Database as the primary source of truth.\n", + "\n", + " 4. Calculations:\n", + " For any required calculations, use only the values provided in the context.\n", + " Provide a brief, clear explanation of the calculations beneath the results.\n", + "\n", + " 5. Response Structure:\n", + " Ensure the response is direct, easy to understand, and well-structured.\n", + " Format the response using Markdown for clarity and readability.\n", + " Use bold sub-headings for clarity where needed. Only use Markdown headings Level 3 (###) and Level 4 (####).\n", + " Use bullet points or numbered lists when appropriate.\n", + " Do not vary the font size within the same sentence.\n", + "\n", + " 6. Citations:\n", + " All factual information used in the answer must be cited with numbered references. For example, [1] should be used to refer to the first source.\n", + " Each citation in the answer must correspond to a single entry in the 'sources' object.\n", + " The same citation and corresponding context chunk may be used multiple times if needed.\n", + " Place the numbered citation at the end of each relevant sentence that uses information from the sources.\n", + " Ensure that each source listed in the 'sources' property is cited at least once in the answer.\n", + " Do not provide a list of definitions from the business glossary; use such information only to enhance the answer contextually.\n", + "\n", + " 7. Citations Format:\n", + " Citations should be embedded within the text, not as a separate list at the end of the 'answer' property.\n", + " [END ANSWER PROPERTY REQUIREMENTS]\n", + "\n", + " [SOURCES PROPERTY REQUIREMENTS]\n", + " 1. Reference Inclusion:\n", + " Include all corresponding references for all cited content in the 'answer' property.\n", + " Place the references in the 'sources' property.\n", + "\n", + " 2. Source Format:\n", + " Each entry in the 'sources' property must be formatted as: {\"title\": \"\", \"chunk\": \"\", \"reference\": \"\"}\n", + " For example, a complete response with two citations would be formatted as: { \"answer\": \"\", \"sources\": [ {\"title\": , \"chunk\": , \"reference\": \"\"}, {\"title\": , \"chunk\": , \"reference\": \"\"} ] }\n", + "\n", + " 3. Source Chunk:\n", + " The 'chunk' property should contain a concise, unedited snippet of the relevant context that supports the answer.\n", + "\n", + " 4. Mandatory References:\n", + " Ensure that every citation in the 'answer' has a corresponding entry in the 'sources' property.\n", + " Every entry in the 'sources' property must be cited at least once in the answer.\n", + " [END SOURCES PROPERTY REQUIREMENTS]\n", + "\n", + "[END RESPONSE OUTPUT REQUIREMENTS]\n", + "\n", + "What are the different product categories we have?What is the top performing product by quantity of units sold?\n", + "Which country did we sell the most to in June 2008? as xml, treating as text, error was: not well-formed (invalid token): line 41, column 78\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=58, prompt_tokens=1878, total_tokens=1936)\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 2 tool calls in parallel.\n", + "INFO:semantic_kernel.kernel:Calling SQL-GetEntitySchema function with args: {\"text\": \"sales by country in June 2008\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema invoking.\n", + "INFO:semantic_kernel.kernel:Calling SQL-GetEntitySchema function with args: {\"text\": \"sales data\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema invoking.\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-03-15-preview \"HTTP/1.1 200 OK\"\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'https://open-ai-vector-db.search.windows.net/indexes('text-2-sql-index')/docs/search.post.search?api-version=REDACTED'\n", + "Request method: 'POST'\n", + "Request headers:\n", + " 'Content-Type': 'application/json'\n", + " 'Content-Length': '34658'\n", + " 'api-key': 'REDACTED'\n", + " 'Accept': 'application/json;odata.metadata=none'\n", + " 'x-ms-client-request-id': '4b5adb26-706a-11ef-ae73-0242ac110002'\n", + " 'User-Agent': 'azsdk-python-search-documents/11.6.0b4 Python/3.12.3 (Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.36)'\n", + "A body is sent with the request\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-03-15-preview \"HTTP/1.1 200 OK\"\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Request URL: 'https://open-ai-vector-db.search.windows.net/indexes('text-2-sql-index')/docs/search.post.search?api-version=REDACTED'\n", + "Request method: 'POST'\n", + "Request headers:\n", + " 'Content-Type': 'application/json'\n", + " 'Content-Length': '34688'\n", + " 'api-key': 'REDACTED'\n", + " 'Accept': 'application/json;odata.metadata=none'\n", + " 'x-ms-client-request-id': '4b65b654-706a-11ef-ae73-0242ac110002'\n", + " 'User-Agent': 'azsdk-python-search-documents/11.6.0b4 Python/3.12.3 (Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.36)'\n", + "A body is sent with the request\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 200\n", + "Response headers:\n", + " 'Transfer-Encoding': 'chunked'\n", + " 'Content-Type': 'application/json; odata.metadata=none; odata.streaming=true; charset=utf-8'\n", + " 'Content-Encoding': 'REDACTED'\n", + " 'Vary': 'REDACTED'\n", + " 'Server': 'Microsoft-IIS/10.0'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Preference-Applied': 'REDACTED'\n", + " 'OData-Version': 'REDACTED'\n", + " 'request-id': '4b5adb26-706a-11ef-ae73-0242ac110002'\n", + " 'elapsed-time': 'REDACTED'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Date': 'Wed, 11 Sep 2024 18:18:52 GMT'\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.427427s\n", + "INFO:azure.core.pipeline.policies.http_logging_policy:Response status: 200\n", + "Response headers:\n", + " 'Transfer-Encoding': 'chunked'\n", + " 'Content-Type': 'application/json; odata.metadata=none; odata.streaming=true; charset=utf-8'\n", + " 'Content-Encoding': 'REDACTED'\n", + " 'Vary': 'REDACTED'\n", + " 'Server': 'Microsoft-IIS/10.0'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Preference-Applied': 'REDACTED'\n", + " 'OData-Version': 'REDACTED'\n", + " 'request-id': '4b65b654-706a-11ef-ae73-0242ac110002'\n", + " 'elapsed-time': 'REDACTED'\n", + " 'Strict-Transport-Security': 'REDACTED'\n", + " 'Date': 'Wed, 11 Sep 2024 18:18:52 GMT'\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-GetEntitySchema succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.501665s\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=74, prompt_tokens=5848, total_tokens=5922)\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 1 tool calls in parallel.\n", + "INFO:semantic_kernel.kernel:Calling SQL-RunSQLQuery function with args: {\"sql_query\":\"SELECT TOP 1 ShipToAddressID, SUM(TotalDue) as TotalSales FROM SalesLT.SalesOrderHeader WHERE OrderDate BETWEEN '2008-06-01' AND '2008-06-30' GROUP BY ShipToAddressID ORDER BY TotalSales DESC;\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-RunSQLQuery invoking.\n", + "INFO:root:Executing SQL Query\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-RunSQLQuery succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.269046s\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 429 Too Many Requests\"\n", + "INFO:openai._base_client:Retrying request to /chat/completions in 17.000000 seconds\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=40, prompt_tokens=5955, total_tokens=5995)\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 1 tool calls in parallel.\n", + "INFO:semantic_kernel.kernel:Calling SQL-RunSQLQuery function with args: {\"sql_query\":\"SELECT AddressLine1, City, StateProvince, CountryRegion FROM SalesLT.Address WHERE AddressID = 659;\"}\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-RunSQLQuery invoking.\n", + "INFO:root:Executing SQL Query\n", + "INFO:semantic_kernel.functions.kernel_function:Function SQL-RunSQLQuery succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.419786s\n", + "INFO:httpx:HTTP Request: POST https://open-ai-gpt-001.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 \"HTTP/1.1 200 OK\"\n", + "INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=266, prompt_tokens=6048, total_tokens=6314)\n", + "INFO:semantic_kernel.functions.kernel_function:Function ChatBot-Chat succeeded.\n", + "INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 25.101880s\n", + "INFO:root:Answer: { \n", + " \"answer\": \"The country where we sold the most in June 2008 is the United Kingdom, specifically to an address in Woolston, England [1][2].\", \n", + " \"sources\": [ \n", + " { \n", + " \"title\": \"Sales Order Header\", \n", + " \"chunk\": \"| ShipToAddressID | TotalSales |\\n|-----------------|----------------|\\n| 659 | 119960.8240 |\\n\", \n", + " \"reference\": \"SELECT TOP 1 ShipToAddressID, SUM(TotalDue) as TotalSales FROM SalesLT.SalesOrderHeader WHERE OrderDate BETWEEN '2008-06-01' AND '2008-06-30' GROUP BY ShipToAddressID ORDER BY TotalSales DESC;\" \n", + " }, \n", + " { \n", + " \"title\": \"Address\", \n", + " \"chunk\": \"| AddressLine1 | City | StateProvince | CountryRegion |\\n|---------------------------|-----------|---------------|---------------|\\n| Warrington Ldc Unit 25/2 | Woolston | England | United Kingdom|\\n\", \n", + " \"reference\": \"SELECT AddressLine1, City, StateProvince, CountryRegion FROM SalesLT.Address WHERE AddressID = 659;\" \n", + " } \n", + " ] \n", + "}\n" + ] + }, + { + "data": { + "text/markdown": [ + "The country where we sold the most in June 2008 is the United Kingdom, specifically to an address in Woolston, England [1][2]." + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "await ask_question(\"Which country did we sell the most to in June 2008?\", history)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernel_info": { + "name": "python310-sdkv2" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.3" + }, + "microsoft": { + "host": { + "AzureML": { + "notebookHasBeenCompleted": true + } + }, + "ms_spell_check": { + "ms_spell_check_language": "en" + } + }, + "nteract": { + "version": "nteract-front-end@1.0.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/text2sql/requirements.txt b/text_2_sql/requirements.txt similarity index 94% rename from text2sql/requirements.txt rename to text_2_sql/requirements.txt index 2916fd3..49829be 100644 --- a/text2sql/requirements.txt +++ b/text_2_sql/requirements.txt @@ -1,6 +1,6 @@ -semantic-kernel==1.8.3 -azure-search -azure-search-documents==11.6.0b4 -aioodbc -azure-identity -python-dotenv +semantic-kernel==1.8.3 +azure-search +azure-search-documents==11.6.0b4 +aioodbc +azure-identity +python-dotenv