From b7ee1e0c2d199a3f5a88aad8901451967b3fb430 Mon Sep 17 00:00:00 2001 From: Ajit Padhi Date: Wed, 19 Nov 2025 12:26:49 +0530 Subject: [PATCH 1/9] Implementation of Configurable Logging Control via Flag --- infra/main.bicep | 8 +--- infra/main.json | 74 ++++++++++++++++++----------- src/api/.env.sample | 7 +++ src/api/api/api_routes.py | 16 ------- src/api/api/history_routes.py | 16 ------- src/api/app.py | 22 +++++++++ src/api/helpers/utils.py | 4 -- src/api/services/chart_service.py | 2 - src/api/services/chat_service.py | 2 - src/api/services/history_service.py | 2 - 10 files changed, 78 insertions(+), 75 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 8c0f434cc..2348abfc3 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -460,12 +460,6 @@ var dnsZoneIndex = { sqlServer: 9 search: 10 } -// List of DNS zone indices that correspond to AI-related services. -var aiRelatedDnsZoneIndices = [ - dnsZoneIndex.cognitiveServices - dnsZoneIndex.openAI - dnsZoneIndex.aiServices -] // =================================================== // DEPLOY PRIVATE DNS ZONES @@ -1571,6 +1565,8 @@ module webSiteBackend 'modules/web-sites.bicep' = { SOLUTION_NAME: solutionSuffix APP_ENV: 'Prod' AZURE_CLIENT_ID: backendUserAssignedIdentity.outputs.clientId + AZURE_BASIC_LOGGING_LEVEL: 'INFO' + AZURE_PACKAGE_LOGGING_LEVEL: 'WARNING' } // WAF aligned configuration for Monitoring applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null diff --git a/infra/main.json b/infra/main.json index 11289e5b7..5b6657bfd 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "13729415444270857669" + "templateHash": "14194719810689418497" } }, "parameters": { @@ -359,13 +359,8 @@ "sqlServer": 9, "search": 10 }, - "aiRelatedDnsZoneIndices": [ - "[variables('dnsZoneIndex').cognitiveServices]", - "[variables('dnsZoneIndex').openAI]", - "[variables('dnsZoneIndex').aiServices]" - ], "userAssignedIdentityResourceName": "[format('id-{0}', variables('solutionSuffix'))]", - "sqlUserAssignedIdentityResourceName": "[format('id-sql-{0}', variables('solutionSuffix'))]", + "backendUserAssignedIdentityResourceName": "[format('id-backend-{0}', variables('solutionSuffix'))]", "keyVaultName": "[format('kv-{0}', variables('solutionSuffix'))]", "existingOpenAIEndpoint": "[if(not(empty(parameters('existingAiFoundryAiProjectResourceId'))), format('https://{0}.openai.azure.com/', split(parameters('existingAiFoundryAiProjectResourceId'), '/')[8]), '')]", "existingProjEndpoint": "[if(not(empty(parameters('existingAiFoundryAiProjectResourceId'))), format('https://{0}.services.ai.azure.com/api/projects/{1}', split(parameters('existingAiFoundryAiProjectResourceId'), '/')[8], split(parameters('existingAiFoundryAiProjectResourceId'), '/')[10]), '')]", @@ -20485,10 +20480,10 @@ } } }, - "sqlUserAssignedIdentity": { + "backendUserAssignedIdentity": { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[take(format('avm.res.managed-identity.user-assigned-identity.{0}', variables('sqlUserAssignedIdentityResourceName')), 64)]", + "name": "[take(format('avm.res.managed-identity.user-assigned-identity.{0}', variables('backendUserAssignedIdentityResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -20496,7 +20491,7 @@ "mode": "Incremental", "parameters": { "name": { - "value": "[variables('sqlUserAssignedIdentityResourceName')]" + "value": "[variables('backendUserAssignedIdentityResourceName')]" }, "location": { "value": "[parameters('location')]" @@ -24333,6 +24328,11 @@ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", "principalType": "ServicePrincipal" }, + { + "roleDefinitionIdOrName": "53ca6127-db72-4b80-b1b0-d745d6d5456d", + "principalId": "[reference('backendUserAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" + }, { "roleDefinitionIdOrName": "64702f94-c441-49e6-a78b-ef80e0188fee", "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", @@ -24342,6 +24342,16 @@ "roleDefinitionIdOrName": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd", "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "64702f94-c441-49e6-a78b-ef80e0188fee", + "principalId": "[reference('backendUserAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd", + "principalId": "[reference('backendUserAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" } ] }, @@ -30052,9 +30062,10 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", + "backendUserAssignedIdentity", "logAnalyticsWorkspace", "userAssignedIdentity", "virtualNetwork" @@ -32381,8 +32392,8 @@ }, "dependsOn": [ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", "logAnalyticsWorkspace", "userAssignedIdentity", "virtualNetwork" @@ -32448,6 +32459,11 @@ "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", "principalType": "ServicePrincipal" }, + { + "roleDefinitionIdOrName": "1407120a-92aa-4202-b7e9-c0e197c71c8f", + "principalId": "[reference('backendUserAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" + }, { "roleDefinitionIdOrName": "1407120a-92aa-4202-b7e9-c0e197c71c8f", "principalId": "[if(not(variables('useExistingAiFoundryAiProject')), reference('aiFoundryAiServices').outputs.aiProjectInfo.value.aiprojectSystemAssignedMIPrincipalId, reference('existingAiFoundryAiServicesProject', '2025-04-01-preview', 'full').identity.principalId)]", @@ -34771,6 +34787,7 @@ "dependsOn": [ "aiFoundryAiServices", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').search)]", + "backendUserAssignedIdentity", "existingAiFoundryAiServicesProject", "logAnalyticsWorkspace", "userAssignedIdentity", @@ -40746,10 +40763,10 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageFile)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageDfs)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageDfs)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", "userAssignedIdentity", "virtualNetwork" ] @@ -40802,7 +40819,7 @@ ], "assignments": [ { - "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]" + "principalId": "[reference('backendUserAssignedIdentity').outputs.principalId.value]" } ] } @@ -44587,9 +44604,9 @@ }, "dependsOn": [ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cosmosDB)]", + "backendUserAssignedIdentity", "logAnalyticsWorkspace", "storageAccount", - "userAssignedIdentity", "virtualNetwork" ] }, @@ -44645,7 +44662,8 @@ "value": { "systemAssigned": true, "userAssignedResourceIds": [ - "[reference('userAssignedIdentity').outputs.resourceId.value]" + "[reference('userAssignedIdentity').outputs.resourceId.value]", + "[reference('backendUserAssignedIdentity').outputs.resourceId.value]" ] } }, @@ -51230,6 +51248,7 @@ }, "dependsOn": [ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').sqlServer)]", + "backendUserAssignedIdentity", "logAnalyticsWorkspace", "userAssignedIdentity", "virtualNetwork" @@ -52437,7 +52456,7 @@ "value": true }, "arguments": { - "value": "[join(createArray(format('-SqlServerName ''{0}''', variables('sqlServerResourceName')), format('-SqlDatabaseName ''{0}''', variables('sqlDbModuleName')), format('-ClientId ''{0}''', reference('sqlUserAssignedIdentity').outputs.clientId.value), format('-DisplayName ''{0}''', reference('sqlUserAssignedIdentity').outputs.name.value), format('-DatabaseRoles ''{0}''', join(variables('databaseRoles'), ','))), ' ')]" + "value": "[join(createArray(format('-SqlServerName ''{0}''', variables('sqlServerResourceName')), format('-SqlDatabaseName ''{0}''', variables('sqlDbModuleName')), format('-ClientId ''{0}''', reference('backendUserAssignedIdentity').outputs.clientId.value), format('-DisplayName ''{0}''', reference('backendUserAssignedIdentity').outputs.name.value), format('-DatabaseRoles ''{0}''', join(variables('databaseRoles'), ','))), ' ')]" }, "scriptContent": { "value": "[variables('$fxv#0')]" @@ -52985,8 +53004,8 @@ } }, "dependsOn": [ + "backendUserAssignedIdentity", "sqlDBModule", - "sqlUserAssignedIdentity", "storageAccount", "userAssignedIdentity", "virtualNetwork" @@ -53022,7 +53041,7 @@ }, "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", "skuName": "[if(or(parameters('enableScalability'), parameters('enableRedundancy')), createObject('value', 'P1v3'), createObject('value', 'B3'))]", - "skuCapacity": "[if(parameters('enableScalability'), createObject('value', 3), createObject('value', 1))]", + "skuCapacity": "[if(parameters('enableScalability'), createObject('value', 1), createObject('value', 1))]", "zoneRedundant": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]" }, "template": { @@ -53592,7 +53611,7 @@ "value": { "systemAssigned": true, "userAssignedResourceIds": [ - "[reference('userAssignedIdentity').outputs.resourceId.value]" + "[reference('backendUserAssignedIdentity').outputs.resourceId.value]" ] } }, @@ -53622,7 +53641,7 @@ "AZURE_COSMOSDB_ENABLE_FEEDBACK": "True", "SQLDB_DATABASE": "[format('sqldb-{0}', variables('solutionSuffix'))]", "SQLDB_SERVER": "[format('{0}{1}', reference('sqlDBModule').outputs.name.value, environment().suffixes.sqlServerHostname)]", - "SQLDB_USER_MID": "[reference('sqlUserAssignedIdentity').outputs.clientId.value]", + "SQLDB_USER_MID": "[reference('backendUserAssignedIdentity').outputs.clientId.value]", "AZURE_AI_SEARCH_ENDPOINT": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]", "AZURE_AI_SEARCH_INDEX": "call_transcripts_index", "AZURE_AI_SEARCH_CONNECTION_NAME": "[variables('aiSearchName')]", @@ -53632,7 +53651,9 @@ "DUMMY_TEST": "True", "SOLUTION_NAME": "[variables('solutionSuffix')]", "APP_ENV": "Prod", - "AZURE_CLIENT_ID": "[reference('userAssignedIdentity').outputs.clientId.value]" + "AZURE_CLIENT_ID": "[reference('backendUserAssignedIdentity').outputs.clientId.value]", + "AZURE_BASIC_LOGGING_LEVEL": "INFO", + "AZURE_PACKAGE_LOGGING_LEVEL": "WARNING" }, "applicationInsightResourceId": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.resourceId.value, null())]" } @@ -55628,11 +55649,10 @@ "dependsOn": [ "aiFoundryAiServices", "applicationInsights", + "backendUserAssignedIdentity", "cosmosDb", "logAnalyticsWorkspace", "sqlDBModule", - "sqlUserAssignedIdentity", - "userAssignedIdentity", "virtualNetwork", "webServerFarm" ] @@ -57876,7 +57896,7 @@ "metadata": { "description": "Contains SQL database user managed identity client ID." }, - "value": "[reference('sqlUserAssignedIdentity').outputs.clientId.value]" + "value": "[reference('backendUserAssignedIdentity').outputs.clientId.value]" }, "USE_AI_PROJECT_CLIENT": { "type": "string", diff --git a/src/api/.env.sample b/src/api/.env.sample index 88fc57119..a6499abad 100644 --- a/src/api/.env.sample +++ b/src/api/.env.sample @@ -24,3 +24,10 @@ SQLDB_DATABASE= SQLDB_SERVER= USE_CHAT_HISTORY_ENABLED="True" APP_ENV="dev" +# Basic application logging (default: INFO level) +AZURE_BASIC_LOGGING_LEVEL=INFO +# Azure package logging (default: WARNING level to suppress INFO) +AZURE_PACKAGE_LOGGING_LEVEL=WARNING +# Comma-separated list of specific logger names to configure (default: empty - no custom loggers) +# Example: AZURE_LOGGING_PACKAGES=azure.identity.aio._internal,azure.monitor.opentelemetry.exporter.export._base +AZURE_LOGGING_PACKAGES= diff --git a/src/api/api/api_routes.py b/src/api/api/api_routes.py index f93203be5..d8bd9b921 100644 --- a/src/api/api/api_routes.py +++ b/src/api/api/api_routes.py @@ -18,8 +18,6 @@ router = APIRouter() -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Check if the Application Insights Instrumentation Key is set in the environment variables @@ -32,20 +30,6 @@ # Log a warning if the Instrumentation Key is not found logging.warning("No Application Insights Instrumentation Key found. Skipping configuration") -# Configure logging -logging.basicConfig(level=logging.INFO) - -# Suppress INFO logs from 'azure.core.pipeline.policies.http_logging_policy' -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel( - logging.WARNING -) -logging.getLogger("azure.identity.aio._internal").setLevel(logging.WARNING) - -# Suppress info logs from OpenTelemetry exporter -logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel( - logging.WARNING -) - @router.get("/fetchChartData") async def fetch_chart_data(): diff --git a/src/api/api/history_routes.py b/src/api/api/history_routes.py index 011e1ec6e..bbbe2c8be 100644 --- a/src/api/api/history_routes.py +++ b/src/api/api/history_routes.py @@ -11,8 +11,6 @@ router = APIRouter() -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Check if the Application Insights Instrumentation Key is set in the environment variables @@ -25,20 +23,6 @@ # Log a warning if the Instrumentation Key is not found logging.warning("No Application Insights Instrumentation Key found. Skipping configuration") -# Configure logging -logging.basicConfig(level=logging.INFO) - -# Suppress INFO logs from 'azure.core.pipeline.policies.http_logging_policy' -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel( - logging.WARNING -) -logging.getLogger("azure.identity.aio._internal").setLevel(logging.WARNING) - -# Suppress info logs from OpenTelemetry exporter -logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel( - logging.WARNING -) - # Single instance of HistoryService (if applicable) history_service = HistoryService() diff --git a/src/api/app.py b/src/api/app.py index 2ba6f1158..0c05661f7 100644 --- a/src/api/app.py +++ b/src/api/app.py @@ -7,6 +7,8 @@ """ +import logging +import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -21,8 +23,28 @@ from api.api_routes import router as backend_router from api.history_routes import router as history_router +# Load environment variables load_dotenv() +# Configure logging +# Basic application logging (default: INFO level) +AZURE_BASIC_LOGGING_LEVEL = os.getenv("AZURE_BASIC_LOGGING_LEVEL", "INFO").upper() +# Azure package logging (default: WARNING level to suppress INFO) +AZURE_PACKAGE_LOGGING_LEVEL = os.getenv("AZURE_PACKAGE_LOGGING_LEVEL", "WARNING").upper() +# Azure logging packages (default: empty list) +azure_logging_packages_env = os.getenv("AZURE_LOGGING_PACKAGES") +AZURE_LOGGING_PACKAGES = azure_logging_packages_env.split(",") if azure_logging_packages_env else [] + +# Basic config: logging.basicConfig(level=logging.INFO) +logging.basicConfig( + level=getattr(logging, AZURE_BASIC_LOGGING_LEVEL, logging.INFO), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +# Package config: Azure loggers set to WARNING to suppress INFO +for logger_name in AZURE_LOGGING_PACKAGES: + logging.getLogger(logger_name).setLevel(getattr(logging, AZURE_PACKAGE_LOGGING_LEVEL, logging.WARNING)) + @asynccontextmanager async def lifespan(fastapi_app: FastAPI): diff --git a/src/api/helpers/utils.py b/src/api/helpers/utils.py index 4c7511d4d..ce854d8c9 100644 --- a/src/api/helpers/utils.py +++ b/src/api/helpers/utils.py @@ -5,10 +5,6 @@ import requests -DEBUG = os.environ.get("DEBUG", "false") -if DEBUG.lower() == "true": - logging.basicConfig(level=logging.DEBUG) - AZURE_SEARCH_PERMITTED_GROUPS_COLUMN = os.environ.get( "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN" ) diff --git a/src/api/services/chart_service.py b/src/api/services/chart_service.py index e1e22a2a3..0b2e60861 100644 --- a/src/api/services/chart_service.py +++ b/src/api/services/chart_service.py @@ -4,8 +4,6 @@ from api.models.input_models import ChartFilters from common.database.sqldb_service import adjust_processed_data_dates, fetch_chart_data, fetch_filters_data -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/src/api/services/chat_service.py b/src/api/services/chat_service.py index a3321a6b5..b7cb7717f 100644 --- a/src/api/services/chat_service.py +++ b/src/api/services/chat_service.py @@ -32,8 +32,6 @@ HOST_NAME = "CKM" HOST_INSTRUCTIONS = "Answer questions about call center operations" -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/src/api/services/history_service.py b/src/api/services/history_service.py index 9eab4a130..43e131dd6 100644 --- a/src/api/services/history_service.py +++ b/src/api/services/history_service.py @@ -9,8 +9,6 @@ from helpers.chat_helper import complete_chat_request from helpers.azure_credential_utils import get_azure_credential -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) From 0eeacfaf7317f47479f37e202bb5c01704f8c442 Mon Sep 17 00:00:00 2001 From: Pavan-Microsoft Date: Mon, 24 Nov 2025 16:18:46 +0530 Subject: [PATCH 2/9] refactor: remove last RAG response handling and optimize API request structure --- src/App/src/App.tsx | 20 +------ src/App/src/api/api.ts | 41 +------------ src/App/src/components/Chat/Chat.tsx | 85 +++++++-------------------- src/App/src/state/ActionConstants.tsx | 1 - src/App/src/state/AppProvider.tsx | 6 -- src/App/src/state/AppReducer.tsx | 10 +--- src/App/src/types/AppTypes.ts | 9 +-- src/api/api/api_routes.py | 2 +- src/api/services/chat_service.py | 35 +++-------- 9 files changed, 33 insertions(+), 176 deletions(-) diff --git a/src/App/src/App.tsx b/src/App/src/App.tsx index cfd69cafb..229db1442 100644 --- a/src/App/src/App.tsx +++ b/src/App/src/App.tsx @@ -234,16 +234,6 @@ const Dashboard: React.FC = () => { const [ASSISTANT, TOOL, ERROR, USER] = ["assistant", "tool", "error", "user"]; - const getLastRagResponse = (messages: ChatMessage[]) => { - const lastAssistantObj = [...messages] - .reverse() - .find((obj) => obj.role === ASSISTANT && typeof obj.content === "string"); - if (typeof lastAssistantObj?.content === "string") { - return lastAssistantObj.content.trim(); - } - return null; - }; - const onSelectConversation = async (id: string) => { if (!id) { console.error("No conversation ID found"); @@ -257,10 +247,6 @@ const Dashboard: React.FC = () => { type: actionConstants.UPDATE_SELECTED_CONV_ID, payload: id, }); - dispatch({ - type: actionConstants.SET_LAST_RAG_RESPONSE, - payload: null, - }); try { const responseMessages = await historyRead(id); @@ -273,11 +259,7 @@ const Dashboard: React.FC = () => { }, }); } - const lastRagResponse = getLastRagResponse(responseMessages); - dispatch({ - type: actionConstants.SET_LAST_RAG_RESPONSE, - payload: lastRagResponse, - }); + } catch (error) { console.error("Error fetching conversation messages:", error); } finally { diff --git a/src/App/src/api/api.ts b/src/App/src/api/api.ts index 7d6c4f3f9..8199dcda0 100644 --- a/src/App/src/api/api.ts +++ b/src/App/src/api/api.ts @@ -287,9 +287,8 @@ export async function callConversationApi( "X-Ms-Client-Principal-Id": userId || "", }, body: JSON.stringify({ - messages: options.messages, - conversation_id: options.id, - last_rag_response: options.last_rag_response + query: options.query, + conversation_id: options.id }), signal: abortSignal, }); @@ -432,42 +431,6 @@ export const historyEnsure = async (): Promise => { return response; }; -export const historyGenerate = async ( - options: ConversationRequest, - abortSignal: AbortSignal, - convId?: string -): Promise => { - let body; - if (convId) { - body = JSON.stringify({ - conversation_id: convId, - messages: options.messages, - }); - } else { - body = JSON.stringify({ - messages: options.messages, - }); - } - const userId = getUserIdFromLocalStorage(); - const response = await fetch(`${baseURL}/history/generate`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-Ms-Client-Principal-Id": userId || "", - }, - body: body, - signal: abortSignal, - }) - .then((res) => { - return res; - }) - .catch((_err) => { - console.error("There was an issue fetching your data."); - return new Response(); - }); - return response; -}; - export const fetchCitationContent = async (body: any) => { try { const response = await fetch(`${baseURL}/api/fetch-azure-search-content`, { diff --git a/src/App/src/components/Chat/Chat.tsx b/src/App/src/components/Chat/Chat.tsx index baf7176ff..e1db0679b 100644 --- a/src/App/src/components/Chat/Chat.tsx +++ b/src/App/src/components/Chat/Chat.tsx @@ -63,8 +63,8 @@ const Chat: React.FC = ({ } }, []); - const saveToDB = async (messages: ChatMessage[], convId: string, reqType: string = 'Text') => { - if (!convId || !messages.length) { + const saveToDB = async (newMessages: ChatMessage[], convId: string, reqType: string = 'Text') => { + if (!convId || !newMessages.length) { return; } const isNewConversation = reqType !== 'graph' ? !state.selectedConversationId : false; @@ -73,14 +73,14 @@ const Chat: React.FC = ({ payload: true, }); - if (((reqType !== 'graph' && reqType !== 'error') && messages[messages.length - 1].role !== ERROR) && isCharthDisplayDefault ){ + if (((reqType !== 'graph' && reqType !== 'error') && newMessages[newMessages.length - 1].role !== ERROR) && isCharthDisplayDefault ){ setIsChartLoading(true); setTimeout(()=>{ - makeApiRequestForChart('show in a graph by default', convId, messages[messages.length - 1].content as string) + makeApiRequestForChart('show in a graph by default', convId, newMessages[newMessages.length - 1].content as string) },5000) } - await historyUpdate(messages, convId) + await historyUpdate(newMessages, convId) .then(async (res) => { if (!res.ok) { if (!messages) { @@ -96,7 +96,7 @@ const Chat: React.FC = ({ const newConversation: Conversation = { id: responseJson?.data?.conversation_id, title: responseJson?.data?.title, - messages: messages, + messages: state.chat.messages, date: responseJson?.data?.date, updatedAt: responseJson?.data?.date, }; @@ -218,10 +218,7 @@ const Chat: React.FC = ({ const request: ConversationRequest = { id: conversationId, - messages: [...state.chat.messages, newMessage].filter( - (messageObj) => messageObj.role !== ERROR - ), - last_rag_response: lrg + query: question }; const streamMessage: ChatMessage = { @@ -271,7 +268,7 @@ const Chat: React.FC = ({ content: errorMsg, date: new Date().toISOString(), }; - updatedMessages = [...state.chat.messages, newMessage, errorMessage]; + updatedMessages = [newMessage, errorMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [errorMessage], @@ -294,11 +291,7 @@ const Chat: React.FC = ({ parsedChartResponse.object as unknown as ChartDataResponse, date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - chartMessage, - ]; + updatedMessages = [newMessage, chartMessage]; // Update messages with the response content dispatch({ type: actionConstants.UPDATE_MESSAGES, @@ -313,11 +306,7 @@ const Chat: React.FC = ({ content: "Error while generating Chart.", date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - chartMessage, - ]; + updatedMessages = [newMessage, chartMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [chartMessage], @@ -357,13 +346,9 @@ const Chat: React.FC = ({ console.log("Caught with an error while chat and save", e); if (abortController.signal.aborted) { if (streamMessage.content) { - updatedMessages = [ - ...state.chat.messages, - newMessage, - ...[streamMessage], - ]; + updatedMessages = [newMessage, streamMessage]; } else { - updatedMessages = [...state.chat.messages, newMessage]; + updatedMessages = [newMessage]; } console.log( "@@@ Abort Signal detected: Formed updated msgs", @@ -429,13 +414,7 @@ const Chat: React.FC = ({ const request: ConversationRequest = { id: conversationId, - messages: [...state.chat.messages, newMessage].filter( - (messageObj) => messageObj.role !== ERROR - ), - last_rag_response: - isChartQuery(userMessage) && state.chat.lastRagResponse - ? JSON.stringify(state.chat.lastRagResponse) - : null, + query: question }; const streamMessage: ChatMessage = { @@ -550,7 +529,7 @@ const Chat: React.FC = ({ content: errorMsg, date: new Date().toISOString(), }; - updatedMessages = [...state.chat.messages, newMessage, errorMessage]; + updatedMessages = [newMessage, errorMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [errorMessage], @@ -593,11 +572,7 @@ const Chat: React.FC = ({ chartResponse as unknown as ChartDataResponse, date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - chartMessage, - ]; + updatedMessages = [newMessage, chartMessage]; // Update messages with the response content dispatch({ type: actionConstants.UPDATE_MESSAGES, @@ -612,11 +587,7 @@ const Chat: React.FC = ({ content: "Error while generating Chart.", date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - chartMessage, - ]; + updatedMessages = [newMessage, chartMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [chartMessage], @@ -646,11 +617,7 @@ const Chat: React.FC = ({ content: errorMsg, date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - errorMessage, - ]; + updatedMessages = [newMessage, errorMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [errorMessage], @@ -661,15 +628,7 @@ const Chat: React.FC = ({ console.log("Error while parsing charts response", e); } } else if (!isChartResponseReceived) { - dispatch({ - type: actionConstants.SET_LAST_RAG_RESPONSE, - payload: streamMessage?.content as string, - }); - updatedMessages = [ - ...state.chat.messages, - newMessage, - ...[streamMessage], - ]; + updatedMessages = [newMessage, streamMessage]; } } if (updatedMessages[updatedMessages.length-1]?.role !== "error") { @@ -679,13 +638,9 @@ const Chat: React.FC = ({ console.log("Caught with an error while chat and save", e); if (abortController.signal.aborted) { if (streamMessage.content) { - updatedMessages = [ - ...state.chat.messages, - newMessage, - ...[streamMessage], - ]; + updatedMessages = [newMessage, streamMessage]; } else { - updatedMessages = [...state.chat.messages, newMessage]; + updatedMessages = [newMessage]; } console.log( "@@@ Abort Signal detected: Formed updated msgs", diff --git a/src/App/src/state/ActionConstants.tsx b/src/App/src/state/ActionConstants.tsx index 0468a9eee..3491fb218 100644 --- a/src/App/src/state/ActionConstants.tsx +++ b/src/App/src/state/ActionConstants.tsx @@ -22,7 +22,6 @@ export const actionConstants = { ADD_NEW_CONVERSATION_TO_CHAT_HISTORY: "ADD_NEW_CONVERSATION_TO_CHAT_HISTORY", UPDATE_APP_SPINNER_STATUS: "UPDATE_APP_SPINNER_STATUS", UPDATE_HISTORY_UPDATE_API_FLAG: "UPDATE_HISTORY_UPDATE_API_FLAG", - SET_LAST_RAG_RESPONSE: "SET_LAST_RAG_RESPONSE", UPDATE_MESSAGE_BY_ID: "UPDATE_MESSAGE_BY_ID", UPDATE_STREAMING_FLAG: "UPDATE_STREAMING_FLAG", UPDATE_CHARTS_FETCHING_FLAG: "UPDATE_CHARTS_FETCHING_FLAG", diff --git a/src/App/src/state/AppProvider.tsx b/src/App/src/state/AppProvider.tsx index 5e66fc3cc..7c812e435 100644 --- a/src/App/src/state/AppProvider.tsx +++ b/src/App/src/state/AppProvider.tsx @@ -28,7 +28,6 @@ export type AppState = { generatingResponse: boolean; messages: ChatMessage[]; userMessage: string; - lastRagResponse: string | null; isStreamingInProgress: boolean; citations: string |null; }; @@ -71,7 +70,6 @@ const initialState: AppState = { generatingResponse: false, messages: [], userMessage: "", - lastRagResponse: null, citations: "", isStreamingInProgress: false, }, @@ -187,10 +185,6 @@ export type Action = type: typeof actionConstants.UPDATE_HISTORY_UPDATE_API_FLAG; payload: boolean; } - | { - type: typeof actionConstants.SET_LAST_RAG_RESPONSE; - payload: string | null; - } | { type: typeof actionConstants.UPDATE_MESSAGE_BY_ID; payload: ChatMessage; diff --git a/src/App/src/state/AppReducer.tsx b/src/App/src/state/AppReducer.tsx index df47521ba..38ca78703 100644 --- a/src/App/src/state/AppReducer.tsx +++ b/src/App/src/state/AppReducer.tsx @@ -88,7 +88,7 @@ const appReducer = (state: AppState, action: Action): AppState => { case actionConstants.NEW_CONVERSATION_START: return { ...state, - chat: { ...state.chat, messages: [], lastRagResponse: null }, + chat: { ...state.chat, messages: [] }, selectedConversationId: "", generatedConversationId: generateUUIDv4(), }; @@ -199,14 +199,6 @@ const appReducer = (state: AppState, action: Action): AppState => { isHistoryUpdateAPIPending: action.payload, }, }; - case actionConstants.SET_LAST_RAG_RESPONSE: - return { - ...state, - chat: { - ...state.chat, - lastRagResponse: action.payload, - }, - }; case actionConstants.UPDATE_MESSAGE_BY_ID: const messageID = action.payload.id; // console.log("aaction::",action.payload) diff --git a/src/App/src/types/AppTypes.ts b/src/App/src/types/AppTypes.ts index 922c4dc9a..0db22ccdb 100644 --- a/src/App/src/types/AppTypes.ts +++ b/src/App/src/types/AppTypes.ts @@ -39,8 +39,7 @@ export type ChatMessage = { export type ConversationRequest = { id?: string; - messages: ChatMessage[]; - last_rag_response: string | null; + query: string; }; export type AskResponse = { @@ -146,10 +145,6 @@ export type HistoryMetaData = { export type ParsedChunk = { error?: string; - id: string; - model: string; - created: number; - object: string; choices: [ { messages: [ @@ -158,10 +153,8 @@ export type ParsedChunk = { role: string; } ]; - history_metadata: object; } ]; - "apim-request-id": string; }; export type ToolMessageContent = { diff --git a/src/api/api/api_routes.py b/src/api/api/api_routes.py index f93203be5..518f77b95 100644 --- a/src/api/api/api_routes.py +++ b/src/api/api/api_routes.py @@ -117,7 +117,7 @@ async def conversation(request: Request): # Get the request JSON and last RAG response from the client request_json = await request.json() conversation_id = request_json.get("conversation_id") - query = request_json.get("messages")[-1].get("content") + query = request_json.get("query") chat_service = ChatService(request=request) result = await chat_service.stream_chat_request(request_json, conversation_id, query) track_event_if_configured( diff --git a/src/api/services/chat_service.py b/src/api/services/chat_service.py index a3321a6b5..a7dc37737 100644 --- a/src/api/services/chat_service.py +++ b/src/api/services/chat_service.py @@ -150,38 +150,17 @@ async def generate(): assistant_content += str(chunk) if assistant_content: - chat_completion_chunk = { - "id": "", - "model": "", - "created": 0, - "object": "", + # Optimized response - only send fields used by frontend + response = { "choices": [ { - "messages": [], - "delta": {}, + "messages": [ + {"role": "assistant", "content": assistant_content} + ] } - ], - "history_metadata": history_metadata, - "apim-request-id": "", + ] } - - chat_completion_chunk["id"] = str(uuid.uuid4()) - chat_completion_chunk["model"] = "rag-model" - chat_completion_chunk["created"] = int(time.time()) - chat_completion_chunk["object"] = "extensions.chat.completion.chunk" - chat_completion_chunk["choices"][0]["messages"].append( - {"role": "assistant", "content": assistant_content} - ) - chat_completion_chunk["choices"][0]["delta"] = { - "role": "assistant", - "content": assistant_content, - } - - completion_chunk_obj = json.loads( - json.dumps(chat_completion_chunk), - object_hook=lambda d: SimpleNamespace(**d), - ) - yield json.dumps(format_stream_response(completion_chunk_obj, history_metadata, "")) + "\n\n" + yield json.dumps(response) + "\n\n" except AgentException as e: error_message = str(e) From 0061d11b9ee1eb832591f4fdde3961bba3b26e8b Mon Sep 17 00:00:00 2001 From: Pavan-Microsoft Date: Mon, 24 Nov 2025 16:18:57 +0530 Subject: [PATCH 3/9] refactor: remove add_conversation endpoint and related tests --- src/api/api/history_routes.py | 26 ------ src/api/services/history_service.py | 36 -------- src/tests/api/api/test_history_routes.py | 18 ---- .../api/services/test_history_service.py | 89 ------------------- 4 files changed, 169 deletions(-) diff --git a/src/api/api/history_routes.py b/src/api/api/history_routes.py index 011e1ec6e..a16ef459d 100644 --- a/src/api/api/history_routes.py +++ b/src/api/api/history_routes.py @@ -43,32 +43,6 @@ history_service = HistoryService() -@router.post("/generate") -async def add_conversation(request: Request): - try: - authenticated_user = get_authenticated_user_details( - request_headers=request.headers) - user_id = authenticated_user["user_principal_id"] - - # Parse request body - request_json = await request.json() - - response = await history_service.add_conversation(user_id, request_json) - track_event_if_configured("ConversationCreated", { - "user_id": user_id, - "request": request_json, - }) - return response - - except Exception as e: - logger.exception("Exception in /generate: %s", str(e)) - span = trace.get_current_span() - if span is not None: - span.record_exception(e) - span.set_status(Status(StatusCode.ERROR, str(e))) - return JSONResponse(content={"error": "An internal error has occurred!"}, status_code=500) - - @router.post("/update") async def update_conversation(request: Request): try: diff --git a/src/api/services/history_service.py b/src/api/services/history_service.py index 9eab4a130..a04d60d83 100644 --- a/src/api/services/history_service.py +++ b/src/api/services/history_service.py @@ -6,7 +6,6 @@ from azure.ai.agents.models import MessageRole, ListSortOrder from common.config.config import Config from common.database.cosmosdb_service import CosmosConversationClient -from helpers.chat_helper import complete_chat_request from helpers.azure_credential_utils import get_azure_credential # Configure logging @@ -123,41 +122,6 @@ async def generate_title(self, conversation_messages): return user_messages[-1]["content"][:50] return "New Conversation" - async def add_conversation(self, user_id: str, request_json: dict): - try: - conversation_id = request_json.get("conversation_id") - messages = request_json.get("messages", []) - - history_metadata = {} - - # make sure cosmos is configured - cosmos_conversation_client = self.init_cosmosdb_client() - if not cosmos_conversation_client: - raise ValueError("CosmosDB is not configured or unavailable") - - if not conversation_id: - title = await self.generate_title(messages) - conversation_dict = await cosmos_conversation_client.create_conversation(user_id, title) - conversation_id = conversation_dict["id"] - history_metadata["title"] = title - history_metadata["date"] = conversation_dict["createdAt"] - - if messages and messages[-1]["role"] == "user": - created_message = await cosmos_conversation_client.create_message(conversation_id, user_id, messages[-1]) - if created_message == "Conversation not found": - raise ValueError( - f"Conversation not found for ID: {conversation_id}") - else: - raise ValueError("No user message found") - - request_body = { - "messages": messages, "history_metadata": { - "conversation_id": conversation_id}} - return await complete_chat_request(request_body) - except Exception: - logger.exception("Error in add_conversation") - raise - async def update_conversation(self, user_id: str, request_json: dict): conversation_id = request_json.get("conversation_id") messages = request_json.get("messages", []) diff --git a/src/tests/api/api/test_history_routes.py b/src/tests/api/api/test_history_routes.py index b054f7236..41ae728d9 100644 --- a/src/tests/api/api/test_history_routes.py +++ b/src/tests/api/api/test_history_routes.py @@ -27,19 +27,6 @@ async def client(): yield ac -@pytest.mark.asyncio -@patch("auth.auth_utils.get_authenticated_user_details") -@patch("services.history_service.HistoryService.add_conversation", new_callable=AsyncMock) -@patch("common.logging.event_utils.track_event_if_configured") -async def test_add_conversation(mock_track, mock_add, mock_auth, client, headers): - mock_auth.return_value = {"user_principal_id": "user123"} - mock_add.return_value = {"result": "ok"} - - res = await client.post("/generate", json={"message": "hello"}, headers=headers) - assert res.status_code == 200 - assert res.json() == {"result": "ok"} - - @pytest.mark.asyncio @patch("auth.auth_utils.get_authenticated_user_details") @patch("services.history_service.HistoryService.update_conversation", new_callable=AsyncMock) @@ -311,8 +298,3 @@ async def test_ensure_cosmos_unknown_error(mock_track, mock_ensure, client): assert res.json()["error"] == "CosmosDB is not configured or not working" -@pytest.mark.asyncio -@patch("auth.auth_utils.get_authenticated_user_details", side_effect=Exception("auth error")) -async def test_add_conversation_exception(mock_auth, client, headers): - res = await client.post("/generate", json={"message": "hi"}, headers=headers) - assert res.status_code == 500 \ No newline at end of file diff --git a/src/tests/api/services/test_history_service.py b/src/tests/api/services/test_history_service.py index 59b84e5bc..2918346d3 100644 --- a/src/tests/api/services/test_history_service.py +++ b/src/tests/api/services/test_history_service.py @@ -146,95 +146,6 @@ async def test_generate_title_exception(self, history_service): result = await history_service.generate_title(conversation_messages) assert result == "Fallback content" - @pytest.mark.asyncio - async def test_add_conversation_new(self, history_service): - """Test adding a new conversation""" - user_id = "test-user-id" - request_json = { - "conversation_id": None, - "messages": [{"role": "user", "content": "Hello"}] - } - - mock_cosmos_client = AsyncMock() - mock_cosmos_client.create_conversation = AsyncMock( - return_value={"id": "new-conv-id", "title": "Test Title", "createdAt": "2023-01-01T00:00:00Z"} - ) - mock_cosmos_client.create_message = AsyncMock(return_value="success") - - with patch.object(history_service, "init_cosmosdb_client", return_value=mock_cosmos_client): - with patch.object(history_service, "generate_title", AsyncMock(return_value="Test Title")): - with patch("services.history_service.complete_chat_request", AsyncMock(return_value={"response": "test"})): - result = await history_service.add_conversation(user_id, request_json) - assert result == {"response": "test"} - - # Verify calls - mock_cosmos_client.create_conversation.assert_awaited_once() - mock_cosmos_client.create_message.assert_awaited_once() - - @pytest.mark.asyncio - async def test_add_conversation_existing(self, history_service): - """Test adding to an existing conversation""" - user_id = "test-user-id" - request_json = { - "conversation_id": "existing-id", - "messages": [{"role": "user", "content": "Hello"}] - } - - mock_cosmos_client = AsyncMock() - mock_cosmos_client.create_message = AsyncMock(return_value="success") - - with patch.object(history_service, "init_cosmosdb_client", return_value=mock_cosmos_client): - with patch("services.history_service.complete_chat_request", AsyncMock(return_value={"response": "test"})): - result = await history_service.add_conversation(user_id, request_json) - assert result == {"response": "test"} - - # Verify calls - mock_cosmos_client.create_message.assert_awaited_once() - - @pytest.mark.asyncio - async def test_add_conversation_cosmos_not_configured(self, history_service): - """Test adding conversation when cosmos is not configured""" - user_id = "test-user-id" - request_json = { - "conversation_id": "existing-id", - "messages": [{"role": "user", "content": "Hello"}] - } - - with patch.object(history_service, "init_cosmosdb_client", return_value=None): - with pytest.raises(ValueError, match="CosmosDB is not configured or unavailable"): - await history_service.add_conversation(user_id, request_json) - - @pytest.mark.asyncio - async def test_add_conversation_no_user_message(self, history_service): - """Test adding conversation with no user message""" - user_id = "test-user-id" - request_json = { - "conversation_id": "existing-id", - "messages": [{"role": "assistant", "content": "Hello"}] - } - - mock_cosmos_client = AsyncMock() - - with patch.object(history_service, "init_cosmosdb_client", return_value=mock_cosmos_client): - with pytest.raises(ValueError, match="No user message found"): - await history_service.add_conversation(user_id, request_json) - - @pytest.mark.asyncio - async def test_add_conversation_conversation_not_found(self, history_service): - """Test adding to a non-existent conversation""" - user_id = "test-user-id" - request_json = { - "conversation_id": "non-existent-id", - "messages": [{"role": "user", "content": "Hello"}] - } - - mock_cosmos_client = AsyncMock() - mock_cosmos_client.create_message = AsyncMock(return_value="Conversation not found") - - with patch.object(history_service, "init_cosmosdb_client", return_value=mock_cosmos_client): - with pytest.raises(ValueError, match="Conversation not found"): - await history_service.add_conversation(user_id, request_json) - @pytest.mark.asyncio async def test_update_conversation(self, history_service): """Test updating an existing conversation""" From ff785d2e1ccecd6c1826bb20128e7e1361d7721b Mon Sep 17 00:00:00 2001 From: Ajit Padhi Date: Tue, 25 Nov 2025 11:13:41 +0530 Subject: [PATCH 4/9] updated code blank value fix --- src/api/app.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/api/app.py b/src/api/app.py index 0c05661f7..4acb2c2bf 100644 --- a/src/api/app.py +++ b/src/api/app.py @@ -26,14 +26,22 @@ # Load environment variables load_dotenv() + +def _get_env_or_default(key: str, default: str) -> str: + """Get environment variable with fallback for empty/whitespace values.""" + value = os.getenv(key) + return value.strip().upper() if value and value.strip() else default.upper() + + # Configure logging # Basic application logging (default: INFO level) -AZURE_BASIC_LOGGING_LEVEL = os.getenv("AZURE_BASIC_LOGGING_LEVEL", "INFO").upper() +AZURE_BASIC_LOGGING_LEVEL = _get_env_or_default("AZURE_BASIC_LOGGING_LEVEL", "INFO") # Azure package logging (default: WARNING level to suppress INFO) -AZURE_PACKAGE_LOGGING_LEVEL = os.getenv("AZURE_PACKAGE_LOGGING_LEVEL", "WARNING").upper() +AZURE_PACKAGE_LOGGING_LEVEL = _get_env_or_default("AZURE_PACKAGE_LOGGING_LEVEL", "WARNING") # Azure logging packages (default: empty list) -azure_logging_packages_env = os.getenv("AZURE_LOGGING_PACKAGES") -AZURE_LOGGING_PACKAGES = azure_logging_packages_env.split(",") if azure_logging_packages_env else [] +AZURE_LOGGING_PACKAGES = [ + pkg.strip() for pkg in os.getenv("AZURE_LOGGING_PACKAGES", "").split(",") if pkg.strip() +] # Basic config: logging.basicConfig(level=logging.INFO) logging.basicConfig( From 79950a23ccab13af0cbf2ecb364a508fb4072e35 Mon Sep 17 00:00:00 2001 From: Ajit Padhi Date: Tue, 25 Nov 2025 11:36:04 +0530 Subject: [PATCH 5/9] minor fix done --- src/api/app.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/api/app.py b/src/api/app.py index 4acb2c2bf..c91e5fbc9 100644 --- a/src/api/app.py +++ b/src/api/app.py @@ -26,18 +26,11 @@ # Load environment variables load_dotenv() - -def _get_env_or_default(key: str, default: str) -> str: - """Get environment variable with fallback for empty/whitespace values.""" - value = os.getenv(key) - return value.strip().upper() if value and value.strip() else default.upper() - - # Configure logging # Basic application logging (default: INFO level) -AZURE_BASIC_LOGGING_LEVEL = _get_env_or_default("AZURE_BASIC_LOGGING_LEVEL", "INFO") +AZURE_BASIC_LOGGING_LEVEL = os.getenv("AZURE_BASIC_LOGGING_LEVEL", "INFO").upper() # Azure package logging (default: WARNING level to suppress INFO) -AZURE_PACKAGE_LOGGING_LEVEL = _get_env_or_default("AZURE_PACKAGE_LOGGING_LEVEL", "WARNING") +AZURE_PACKAGE_LOGGING_LEVEL = os.getenv("AZURE_PACKAGE_LOGGING_LEVEL", "WARNING").upper() # Azure logging packages (default: empty list) AZURE_LOGGING_PACKAGES = [ pkg.strip() for pkg in os.getenv("AZURE_LOGGING_PACKAGES", "").split(",") if pkg.strip() From e008e8a773911a755bc0f56230f4133faf6122f2 Mon Sep 17 00:00:00 2001 From: Ajit Padhi Date: Tue, 25 Nov 2025 11:40:15 +0530 Subject: [PATCH 6/9] updated bicep --- infra/main.bicep | 1 + infra/main.json | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 2348abfc3..d567da28f 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -1567,6 +1567,7 @@ module webSiteBackend 'modules/web-sites.bicep' = { AZURE_CLIENT_ID: backendUserAssignedIdentity.outputs.clientId AZURE_BASIC_LOGGING_LEVEL: 'INFO' AZURE_PACKAGE_LOGGING_LEVEL: 'WARNING' + AZURE_LOGGING_PACKAGES: '' } // WAF aligned configuration for Monitoring applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null diff --git a/infra/main.json b/infra/main.json index 5b6657bfd..067ef292a 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "14194719810689418497" + "templateHash": "11248839133370877384" } }, "parameters": { @@ -30062,9 +30062,9 @@ } }, "dependsOn": [ + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", "backendUserAssignedIdentity", "logAnalyticsWorkspace", "userAssignedIdentity", @@ -53653,7 +53653,8 @@ "APP_ENV": "Prod", "AZURE_CLIENT_ID": "[reference('backendUserAssignedIdentity').outputs.clientId.value]", "AZURE_BASIC_LOGGING_LEVEL": "INFO", - "AZURE_PACKAGE_LOGGING_LEVEL": "WARNING" + "AZURE_PACKAGE_LOGGING_LEVEL": "WARNING", + "AZURE_LOGGING_PACKAGES": "" }, "applicationInsightResourceId": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.resourceId.value, null())]" } From 598cf36c00656140abfcccb6faa7f86727153683 Mon Sep 17 00:00:00 2001 From: Pavan-Microsoft Date: Thu, 27 Nov 2025 13:47:52 +0530 Subject: [PATCH 7/9] refactor: remove complete_chat_request method and related tests from ChatService and TestChatService --- src/api/services/chat_service.py | 24 -------- src/tests/api/services/test_chat_service.py | 59 ++----------------- .../api/services/test_history_service.py | 5 +- 3 files changed, 8 insertions(+), 80 deletions(-) diff --git a/src/api/services/chat_service.py b/src/api/services/chat_service.py index a7dc37737..a3f1f3807 100644 --- a/src/api/services/chat_service.py +++ b/src/api/services/chat_service.py @@ -180,27 +180,3 @@ async def generate(): yield json.dumps({"error": "An error occurred while processing the request."}) + "\n\n" return generate() - - async def complete_chat_request(self, query, last_rag_response=None): - """ - Completes a chat request by generating a chart from the RAG response. - """ - if not last_rag_response: - return {"error": "A previous RAG response is required to generate a chart."} - - # Process RAG response to generate chart data - chart_data = await self.process_rag_response(last_rag_response, query) - - if not chart_data or "error" in chart_data: - return { - "error": "Chart could not be generated from this data. Please ask a different question.", - "error_desc": str(chart_data), - } - - logger.info("Successfully generated chart data.") - return { - "id": str(uuid.uuid4()), - "model": "azure-openai", - "created": int(time.time()), - "object": chart_data, - } diff --git a/src/tests/api/services/test_chat_service.py b/src/tests/api/services/test_chat_service.py index ffb45b4d8..6ebbebe14 100644 --- a/src/tests/api/services/test_chat_service.py +++ b/src/tests/api/services/test_chat_service.py @@ -5,7 +5,6 @@ import pytest from fastapi import HTTPException, status from semantic_kernel.exceptions.agent_exceptions import AgentException as RealAgentException -from azure.ai.agents.models import MessageRole @@ -244,16 +243,8 @@ async def mock_invoke_stream(*args, **kwargs): assert "I cannot answer this question with the current data" in chunks[0] @pytest.mark.asyncio - @patch('services.chat_service.uuid.uuid4') - @patch('services.chat_service.time.time') - @patch('services.chat_service.format_stream_response') - async def test_stream_chat_request_success(self, mock_format_stream, mock_time, mock_uuid, chat_service): + async def test_stream_chat_request_success(self, chat_service): """Test successful stream chat request.""" - # Setup mocks - mock_uuid.return_value = "test-uuid" - mock_time.return_value = 1234567890 - mock_format_stream.return_value = {"formatted": "response"} - # Mock stream_openai_text async def mock_stream_openai_text(conversation_id, query): yield "Hello" @@ -272,7 +263,11 @@ async def mock_stream_openai_text(conversation_id, query): # Verify the chunks contain expected structure for chunk in chunks: chunk_data = json.loads(chunk.strip()) - assert "formatted" in chunk_data + assert "choices" in chunk_data + assert len(chunk_data["choices"]) > 0 + assert "messages" in chunk_data["choices"][0] + assert len(chunk_data["choices"][0]["messages"]) > 0 + assert chunk_data["choices"][0]["messages"][0]["role"] == "assistant" @pytest.mark.asyncio async def test_stream_chat_request_agent_exception_rate_limit(self, chat_service): @@ -345,46 +340,4 @@ async def mock_stream_openai_text_generic_error(conversation_id, query): error_data = json.loads(chunks[0].strip()) assert "error" in error_data assert "An error occurred while processing the request." == error_data["error"] - - @pytest.mark.asyncio - async def test_complete_chat_request_success(self, chat_service): - mock_chart_data = { - "type": "bar", - "data": { - "labels": ["A"], - "datasets": [{"data": [1]}] - } - } - - chat_service.process_rag_response = AsyncMock(return_value=mock_chart_data) - - result = await chat_service.complete_chat_request("Query", last_rag_response="RAG response") - - assert result["object"]["type"] == "bar" - - - @pytest.mark.asyncio - async def test_complete_chat_request_no_rag_response(self, chat_service): - """Test complete chat request without RAG response.""" - result = await chat_service.complete_chat_request("Query", last_rag_response=None) - - assert "error" in result - assert result["error"] == "A previous RAG response is required to generate a chart." - - @pytest.mark.asyncio - async def test_complete_chat_request_chart_error(self, chat_service): - chat_service.process_rag_response = AsyncMock(return_value={"error": "Chart generation failed"}) - - result = await chat_service.complete_chat_request("Query", last_rag_response="RAG response") - - assert "error" in result - - - @pytest.mark.asyncio - async def test_complete_chat_request_empty_chart_data(self, chat_service): - chat_service.process_rag_response = AsyncMock(return_value=None) - - result = await chat_service.complete_chat_request("Query", last_rag_response="RAG response") - - assert "error" in result diff --git a/src/tests/api/services/test_history_service.py b/src/tests/api/services/test_history_service.py index 2918346d3..5626bb78b 100644 --- a/src/tests/api/services/test_history_service.py +++ b/src/tests/api/services/test_history_service.py @@ -30,9 +30,8 @@ def history_service(mock_config_instance): with patch("services.history_service.Config", return_value=mock_config_instance): # Create patches for other dependencies used by HistoryService with patch("services.history_service.CosmosConversationClient"): - with patch("services.history_service.complete_chat_request"): - service = HistoryService() - return service + service = HistoryService() + return service @pytest.fixture From f1bff72a3f15c751f04b6d60a1d2353b0b33f65e Mon Sep 17 00:00:00 2001 From: Pavan-Microsoft Date: Fri, 28 Nov 2025 10:38:42 +0530 Subject: [PATCH 8/9] refactor: simplify stream_chat_request method by removing unnecessary request_body parameter --- src/api/api/api_routes.py | 2 +- src/api/services/chat_service.py | 7 +------ src/tests/api/services/test_chat_service.py | 12 ++++-------- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/api/api/api_routes.py b/src/api/api/api_routes.py index 518f77b95..80cf483a2 100644 --- a/src/api/api/api_routes.py +++ b/src/api/api/api_routes.py @@ -119,7 +119,7 @@ async def conversation(request: Request): conversation_id = request_json.get("conversation_id") query = request_json.get("query") chat_service = ChatService(request=request) - result = await chat_service.stream_chat_request(request_json, conversation_id, query) + result = await chat_service.stream_chat_request(conversation_id, query) track_event_if_configured( "ChatStreamSuccess", {"conversation_id": conversation_id, "query": query} diff --git a/src/api/services/chat_service.py b/src/api/services/chat_service.py index a3f1f3807..2bb55866f 100644 --- a/src/api/services/chat_service.py +++ b/src/api/services/chat_service.py @@ -8,9 +8,6 @@ import json import logging -import time -import uuid -from types import SimpleNamespace import asyncio import random import re @@ -25,7 +22,6 @@ from cachetools import TTLCache -from helpers.utils import format_stream_response from common.config.config import Config # Constants @@ -135,11 +131,10 @@ async def stream_openai_text(self, conversation_id: str, query: str) -> Streamin ChatService.thread_cache[corrupt_key] = thread_id yield "I cannot answer this question with the current data. Please rephrase or add more details." - async def stream_chat_request(self, request_body, conversation_id, query): + async def stream_chat_request(self, conversation_id, query): """ Handles streaming chat requests. """ - history_metadata = request_body.get("history_metadata", {}) async def generate(): try: diff --git a/src/tests/api/services/test_chat_service.py b/src/tests/api/services/test_chat_service.py index 6ebbebe14..09f091ff9 100644 --- a/src/tests/api/services/test_chat_service.py +++ b/src/tests/api/services/test_chat_service.py @@ -252,8 +252,7 @@ async def mock_stream_openai_text(conversation_id, query): chat_service.stream_openai_text = mock_stream_openai_text - request_body = {"history_metadata": {"test": "metadata"}} - generator = await chat_service.stream_chat_request(request_body, "conv_1", "Hello") + generator = await chat_service.stream_chat_request("conv_1", "Hello") chunks = [] async for chunk in generator: @@ -280,8 +279,7 @@ async def mock_stream_openai_text_rate_limit_error(conversation_id, query): chat_service.stream_openai_text = mock_stream_openai_text_rate_limit_error - request_body = {"history_metadata": {}} - generator = await chat_service.stream_chat_request(request_body, "conv_1", "Hello") + generator = await chat_service.stream_chat_request("conv_1", "Hello") chunks = [] async for chunk in generator: @@ -304,8 +302,7 @@ async def mock_stream_openai_text_generic_error(conversation_id, query): chat_service.stream_openai_text = mock_stream_openai_text_generic_error - request_body = {"history_metadata": {}} - generator = await chat_service.stream_chat_request(request_body, "conv_1", "Hello") + generator = await chat_service.stream_chat_request("conv_1", "Hello") chunks = [] async for chunk in generator: @@ -328,8 +325,7 @@ async def mock_stream_openai_text_generic_error(conversation_id, query): chat_service.stream_openai_text = mock_stream_openai_text_generic_error - request_body = {"history_metadata": {}} - generator = await chat_service.stream_chat_request(request_body, "conv_1", "Hello") + generator = await chat_service.stream_chat_request("conv_1", "Hello") chunks = [] async for chunk in generator: From 786c4ab7025fea028ea069c9a65e8209f24a8a1a Mon Sep 17 00:00:00 2001 From: Pavan-Microsoft Date: Fri, 28 Nov 2025 15:10:30 +0530 Subject: [PATCH 9/9] fix: update identity query to exclude 'id-backend-' prefix --- infra/scripts/run_process_data_scripts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/scripts/run_process_data_scripts.sh b/infra/scripts/run_process_data_scripts.sh index 94b31fc33..77a1b32be 100644 --- a/infra/scripts/run_process_data_scripts.sh +++ b/infra/scripts/run_process_data_scripts.sh @@ -25,7 +25,7 @@ echo "Fetching Key Vault and Managed Identity from resource group: $resourceGrou keyVaultName=$(az keyvault list --resource-group "$resourceGroupName" --query "[0].name" -o tsv) # === Retrieve the ID of the first user-assigned identity with name starting with 'id-' === -managedIdentityResourceId=$(az identity list --resource-group "$resourceGroupName" --query "[?starts_with(name, 'id-') && !starts_with(name, 'id-sql-')].id | [0]" -o tsv) +managedIdentityResourceId=$(az identity list --resource-group "$resourceGroupName" --query "[?starts_with(name, 'id-') && !starts_with(name, 'id-backend-')].id | [0]" -o tsv) # === Normalize managedIdentityResourceId (necessary for compatibility in Git Bash on Windows) === managedIdentityResourceId=$(echo "$managedIdentityResourceId" | sed -E 's|.*(/subscriptions/)|\1|') @@ -34,7 +34,7 @@ managedIdentityResourceId=$(echo "$managedIdentityResourceId" | sed -E 's|.*(/su sqlServerLocation=$(az sql server list --resource-group "$resourceGroupName" --query "[0].location" -o tsv) # === Retrieve the principal ID of the first user-assigned identity with name starting with 'id-' === -managedIdentityClientId=$(az identity list --resource-group "$resourceGroupName" --query "[?starts_with(name, 'id-') && !starts_with(name, 'id-sql-')].clientId | [0]" -o tsv) +managedIdentityClientId=$(az identity list --resource-group "$resourceGroupName" --query "[?starts_with(name, 'id-') && !starts_with(name, 'id-backend-')].clientId | [0]" -o tsv) # === Check for VNet deployment === echo "Checking for VNet deployment in resource group: $resourceGroupName"