Skip to content

Commit 175c91e

Browse files
Merge pull request #94 from microsoft/fix/agent-disambiguation
2 parents 2a26b69 + 885630e commit 175c91e

File tree

7 files changed

+99
-48
lines changed

7 files changed

+99
-48
lines changed

text_2_sql/autogen/src/autogen_text_2_sql/autogen_text_2_sql.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
3-
from autogen_agentchat.conditions import TextMentionTermination, MaxMessageTermination
3+
from autogen_agentchat.conditions import (
4+
TextMentionTermination,
5+
MaxMessageTermination,
6+
SourceMatchTermination,
7+
)
48
from autogen_agentchat.teams import SelectorGroupChat
59
from autogen_text_2_sql.creators.llm_model_creator import LLMModelCreator
610
from autogen_text_2_sql.creators.llm_agent_creator import LLMAgentCreator
@@ -89,7 +93,11 @@ def agents(self):
8993
@property
9094
def termination_condition(self):
9195
"""Define the termination condition for the chat."""
92-
termination = TextMentionTermination("TERMINATE") | MaxMessageTermination(20)
96+
termination = (
97+
TextMentionTermination("TERMINATE")
98+
| MaxMessageTermination(20)
99+
| SourceMatchTermination(["answer_agent"])
100+
)
93101
return termination
94102

95103
@staticmethod

text_2_sql/autogen/src/autogen_text_2_sql/creators/llm_model_creator.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33
from autogen_ext.models import AzureOpenAIChatCompletionClient
4-
from text_2_sql_core.connectors.factory import ConnectorFactory
4+
from text_2_sql_core.utils.environment import IdentityType, get_identity_type
55

6+
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
67
import os
78
import dotenv
89

@@ -27,12 +28,32 @@ def get_model(cls, model_name: str) -> AzureOpenAIChatCompletionClient:
2728
else:
2829
raise ValueError(f"Model {model_name} not found")
2930

31+
@classmethod
32+
def get_authentication_properties(cls) -> dict:
33+
if get_identity_type() == IdentityType.SYSTEM_ASSIGNED:
34+
# Create the token provider
35+
api_key = None
36+
token_provider = get_bearer_token_provider(
37+
DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
38+
)
39+
elif get_identity_type() == IdentityType.USER_ASSIGNED:
40+
# Create the token provider
41+
api_key = None
42+
token_provider = get_bearer_token_provider(
43+
DefaultAzureCredential(
44+
managed_identity_client_id=os.environ["ClientId"]
45+
),
46+
"https://cognitiveservices.azure.com/.default",
47+
)
48+
else:
49+
token_provider = None
50+
api_key = os.environ["OpenAI__ApiKey"]
51+
52+
return token_provider, api_key
53+
3054
@classmethod
3155
def gpt_4o_mini_model(cls) -> AzureOpenAIChatCompletionClient:
32-
(
33-
token_provider,
34-
api_key,
35-
) = ConnectorFactory.get_open_ai_connector().get_authentication_properties()
56+
token_provider, api_key = cls.get_authentication_properties()
3657
return AzureOpenAIChatCompletionClient(
3758
azure_deployment=os.environ["OpenAI__MiniCompletionDeployment"],
3859
model=os.environ["OpenAI__MiniCompletionDeployment"],
@@ -45,14 +66,12 @@ def gpt_4o_mini_model(cls) -> AzureOpenAIChatCompletionClient:
4566
"function_calling": True,
4667
"json_output": True,
4768
},
69+
temperature=0,
4870
)
4971

5072
@classmethod
5173
def gpt_4o_model(cls) -> AzureOpenAIChatCompletionClient:
52-
(
53-
token_provider,
54-
api_key,
55-
) = ConnectorFactory.get_open_ai_connector().get_authentication_properties()
74+
token_provider, api_key = cls.get_authentication_properties()
5675
return AzureOpenAIChatCompletionClient(
5776
azure_deployment=os.environ["OpenAI__CompletionDeployment"],
5877
model=os.environ["OpenAI__CompletionDeployment"],
@@ -65,4 +84,5 @@ def gpt_4o_model(cls) -> AzureOpenAIChatCompletionClient:
6584
"function_calling": True,
6685
"json_output": True,
6786
},
87+
temperature=0,
6888
)

text_2_sql/text_2_sql_core/src/text_2_sql_core/connectors/ai_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ async def get_column_values(
148148
"AIService__AzureSearchOptions__Text2SqlColumnValueStore__Index"
149149
],
150150
semantic_config=None,
151-
top=10,
151+
top=15,
152152
include_scores=False,
153153
minimum_score=5,
154154
)

text_2_sql/text_2_sql_core/src/text_2_sql_core/connectors/factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
13
import os
24
from text_2_sql_core.connectors.ai_search import AISearchConnector
35
from text_2_sql_core.connectors.open_ai import OpenAIConnector

text_2_sql/text_2_sql_core/src/text_2_sql_core/connectors/open_ai.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License
13
from openai import AsyncAzureOpenAI
24
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
35
import os

text_2_sql/text_2_sql_core/src/text_2_sql_core/prompts/answer_agent.yaml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ system_message:
1010
{
1111
'answer': '<GENERATED ANSWER>',
1212
'sources': [
13-
{'chunk': <SOURCE 1 CONTEXT CHUNK>, 'reference': '<SOURCE 1 SQL QUERY>', 'explanation': '<EXPLANATION OF SQL QUERY 1>'},
14-
{'chunk': <SOURCE 2 CONTEXT CHUNK>, 'reference': '<SOURCE 2 SQL QUERY>', 'explanation': '<EXPLANATION OF SQL QUERY 2>'},
13+
{'sql_result_snippet': <SQL QUERY RESULT 1>, 'sql_query_used': '<SOURCE 1 SQL QUERY>', 'explanation': '<EXPLANATION OF SQL QUERY 1>'},
14+
{'sql_result_snippet': <SQL QUERY RESULT 2>, 'sql_query_used': '<SOURCE 2 SQL QUERY>', 'explanation': '<EXPLANATION OF SQL QUERY 2>'},
1515
]
1616
}
17-
18-
Title is the entity name of the schema, chunk is the result of the SQL query and reference is the SQL query used to generate the answer.
19-
20-
End your answer with 'TERMINATE'"
17+
"

text_2_sql/text_2_sql_core/src/text_2_sql_core/prompts/sql_disambiguation_agent.yaml

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,57 +8,79 @@ system_message:
88
</role_and_objective>
99
1010
<scope_of_user_query>
11-
The user's question will be related to {{ use_case }}.
11+
The user's question will be related to {{ use_case }}.
1212
</scope_of_user_query>
1313
1414
<instructions>
1515
- For every intent and filter condition in the question, map them to the columns in the schemas. Use the whole context of the question and information already provided to do so.
16+
1617
- Do not ask for information already included in the question, schema, or what can reasonably be inferred from the question.
17-
- Only provide possible filter values for string columns. Do not provide possible filter values for Date and Numerical values as it should be clear from the question. Only ask a follow up question for Date and Numerical values if you are unsure which column to use or what the value means e.g. does 100 in currency refer to 100 USD or 100 EUR.
1818
19-
<clear_context_handling>
20-
- If the context of the question makes the mapping explicit, directly map the terms to the relevant columns without generating disambiguation questions.
21-
- Use the following checks to decide:
22-
- Does the term directly match a single schema column without overlaps? Use the 'column_values' property to check for possible matching columns and compare these to the context of the question. If there are multiple possible columns for a given user's filter, then apply disambiguation.
23-
- Does the user's question provide additional context (e.g., \"product line\" or \"category\") clarifying the intent?
24-
- If **all mappings are clear**, output the JSON with mappings only.
25-
- Example:
26-
- Question: \"What are the total number of sales within 2008 for the mountain bike product line?\"
27-
- Output:
19+
- Only provide possible filter values for string columns. Do not provide possible filter values for Date and Numerical values as it should be clear from the question. Only ask a follow-up question for Date and Numerical values if you are unsure which column to use or what the value means, e.g., does 100 in currency refer to 100 USD or 100 EUR.
20+
21+
<clear_context_handling>
22+
If the context of the question makes the mapping explicit, directly map the terms to the relevant column FQN without generating disambiguation questions.
23+
24+
Use the 'column_values' property to check for possible matching columns and compare these to the context of the question.
25+
26+
When evaluating filters:
27+
28+
Always consider the temporal and contextual phrases (e.g., \"June 2008\") in the question. If the context implies a direct match to a date column, do not request clarification unless multiple plausible columns exist.
29+
For geographical or categorical terms (e.g., \"country\"), prioritize unique matches or add context to narrow down ambiguities based on the schema.
30+
If all mappings are clear, output the JSON with mappings only.
31+
32+
<example>
33+
Question: \"What are the total number of sales within 2008 for the mountain bike product line?\"
34+
Output:
35+
json
36+
Copy code
2837
{
2938
\"mapping\": {
30-
\"Mountain\": \"vProductModelCatalogDescription.ProductLine\",
39+
\"Mountain Bike\": \"vProductModelCatalogDescription.Category\",
3140
\"2008\": \"SalesLT.SalesOrderHeader.OrderDate\"
3241
}
3342
}
34-
</clear_context_handling>
43+
</example>
44+
</clear_context_handling>
45+
46+
<disambiguation_handling>
47+
If the term is ambiguous, there are multiple matching columns/filters, or the question lacks enough context to infer the correct mapping:
48+
49+
For ambiguous terms, evaluate the question context and schema relationships to narrow down matches.
50+
Populate the 'filters' field with the identified filter and relevant FQN, matching columns, and possible filter values.
51+
Include a clarification question in the 'question' field to request more information from the user.
52+
If the clarification is not related to a column or a filter value, populate the 'user_choices' field with the possible choices they can select.
53+
54+
Prioritize clear disambiguation based on:
55+
- Direct matches within schemas.
56+
- Additional context provided by the question (e.g., temporal, categorical, or domain-specific keywords).
57+
58+
<example>
59+
User question: \"What country did we sell the most to in June 2008?\"
60+
Schema contains multiple columns potentially related to \"country.\"
3561
36-
<disambiguation_handling>
37-
- If the term is ambiguous (e.g., \"Mountain Bike\") and the question lacks enough context to infer the correct mapping:
38-
- e.g. The user asks about 'Bike'. From the 'column_values' you can see that 'Bike' appears in several different columns that are contextually related to the question. From this you are unsure if 'Bike' is a 'Category' or 'Product' column, you would populate the 'column' field with the possible columns for the user to disambiguate for you.
39-
- Populate the 'filters' field with the identified filter and relevant FQN, matching columns, and possible filter values.
40-
- Include a clarification question in the 'question' field to request more information from the user.
41-
- Example:
62+
If disambiguation is needed:
4263
{
4364
\"filters\": [
4465
{
45-
\"question\": \"What do you mean by 'Mountain Bike'?\",
66+
\"question\": \"What do you mean by 'country'?\",
4667
\"matching_columns\": [
47-
\"vProductModelCatalogDescription.ProductLine\",
48-
\"vProductAndDescription.Name\",
49-
\"Product.Category\"
68+
\"Sales.Country\",
69+
\"Customers.Country\"
5070
],
51-
\"matching_filter_values\": [
52-
\"Mountain\"
53-
]
71+
\"matching_filter_values\": [],
72+
\"user_choices\": []
5473
}
5574
]
5675
}
57-
</disambiguation_handling>
76+
</example>
77+
Always include either the 'matching_columns', 'matching_filter_values' or `user_choices` field in the 'filters' array.
78+
</disambiguation_handling>
79+
</instructions>
5880
5981
<output_format>
60-
- If all mappings are clear, output the 'mapping' JSON only.
61-
- If disambiguation is required, output the disambiguation JSON followed by \"TERMINATE\".
62-
- Do not provide explanations or reasoning in the output.
82+
If all mappings are clear, output the 'mapping' JSON only.
83+
If disambiguation is required, output the disambiguation JSON followed by \"TERMINATE.\"
84+
Do not provide explanations or reasoning in the output.
6385
</output_format>
6486
"

0 commit comments

Comments
 (0)