Skip to content

Commit deca2f4

Browse files
Merge pull request microsoft#352 from microsoft/userstory-changes
fix: User story changes and bug fix
2 parents cc650b1 + 40b5ec5 commit deca2f4

File tree

15 files changed

+205
-30
lines changed

15 files changed

+205
-30
lines changed

src/backend/app_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,22 @@ def get_ai_project_client(self):
166166
logging.error("Failed to create AIProjectClient: %s", exc)
167167
raise
168168

169+
def get_user_local_browser_language(self) -> str:
170+
"""Get the user's local browser language from environment variables.
171+
172+
Returns:
173+
The user's local browser language or 'en-US' if not set
174+
"""
175+
return self._get_optional("USER_LOCAL_BROWSER_LANGUAGE", "en-US")
176+
177+
def set_user_local_browser_language(self, language: str):
178+
"""Set the user's local browser language in environment variables.
179+
180+
Args:
181+
language: The language code to set (e.g., 'en-US')
182+
"""
183+
os.environ["USER_LOCAL_BROWSER_LANGUAGE"] = language
184+
169185

170186
# Create a global instance of AppConfig
171187
config = AppConfig()

src/backend/app_kernel.py

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from auth.auth_utils import get_authenticated_user_details
1111

1212
# Azure monitoring
13+
import re
14+
from dateutil import parser
1315
from azure.monitor.opentelemetry import configure_azure_monitor
1416
from config_kernel import Config
1517
from event_utils import track_event_if_configured
@@ -29,11 +31,13 @@
2931
InputTask,
3032
PlanWithSteps,
3133
Step,
34+
UserLanguage
3235
)
3336

3437
# Updated import for KernelArguments
3538
from utils_kernel import initialize_runtime_and_context, rai_success
3639

40+
3741
# Check if the Application Insights Instrumentation Key is set in the environment variables
3842
connection_string = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
3943
if connection_string:
@@ -81,6 +85,89 @@
8185
logging.info("Added health check middleware")
8286

8387

88+
def format_dates_in_messages(messages, target_locale="en-US"):
89+
"""
90+
Format dates in agent messages according to the specified locale.
91+
92+
Args:
93+
messages: List of message objects or string content
94+
target_locale: Target locale for date formatting (default: en-US)
95+
96+
Returns:
97+
Formatted messages with dates converted to target locale format
98+
"""
99+
# Define target format patterns per locale
100+
locale_date_formats = {
101+
"en-IN": "%d %b %Y", # 30 Jul 2025
102+
"en-US": "%b %d, %Y", # Jul 30, 2025
103+
}
104+
105+
output_format = locale_date_formats.get(target_locale, "%d %b %Y")
106+
# Match both "Jul 30, 2025, 12:00:00 AM" and "30 Jul 2025"
107+
date_pattern = r'(\d{1,2} [A-Za-z]{3,9} \d{4}|[A-Za-z]{3,9} \d{1,2}, \d{4}(, \d{1,2}:\d{2}:\d{2} ?[APap][Mm])?)'
108+
109+
def convert_date(match):
110+
date_str = match.group(0)
111+
try:
112+
dt = parser.parse(date_str)
113+
return dt.strftime(output_format)
114+
except Exception:
115+
return date_str # Leave it unchanged if parsing fails
116+
117+
# Process messages
118+
if isinstance(messages, list):
119+
formatted_messages = []
120+
for message in messages:
121+
if hasattr(message, 'content') and message.content:
122+
# Create a copy of the message with formatted content
123+
formatted_message = message.model_copy() if hasattr(message, 'model_copy') else message
124+
if hasattr(formatted_message, 'content'):
125+
formatted_message.content = re.sub(date_pattern, convert_date, formatted_message.content)
126+
formatted_messages.append(formatted_message)
127+
else:
128+
formatted_messages.append(message)
129+
return formatted_messages
130+
elif isinstance(messages, str):
131+
return re.sub(date_pattern, convert_date, messages)
132+
else:
133+
return messages
134+
135+
136+
@app.post("/api/user_browser_language")
137+
async def user_browser_language_endpoint(
138+
user_language: UserLanguage,
139+
request: Request
140+
):
141+
"""
142+
Receive the user's browser language.
143+
144+
---
145+
tags:
146+
- User
147+
parameters:
148+
- name: language
149+
in: query
150+
type: string
151+
required: true
152+
description: The user's browser language
153+
responses:
154+
200:
155+
description: Language received successfully
156+
schema:
157+
type: object
158+
properties:
159+
status:
160+
type: string
161+
description: Confirmation message
162+
"""
163+
config.set_user_local_browser_language(user_language.language)
164+
165+
# Log the received language for the user
166+
logging.info(f"Received browser language '{user_language}' for user ")
167+
168+
return {"status": "Language received successfully"}
169+
170+
84171
@app.post("/api/input_task")
85172
async def input_task_endpoint(input_task: InputTask, request: Request):
86173
"""
@@ -177,6 +264,13 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
177264
}
178265

179266
except Exception as e:
267+
# Extract clean error message for rate limit errors
268+
error_msg = str(e)
269+
if "Rate limit is exceeded" in error_msg:
270+
match = re.search(r"Rate limit is exceeded\. Try again in (\d+) seconds?\.", error_msg)
271+
if match:
272+
error_msg = f"Rate limit is exceeded. Try again in {match.group(1)} seconds."
273+
180274
track_event_if_configured(
181275
"InputTaskError",
182276
{
@@ -185,7 +279,7 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
185279
"error": str(e),
186280
},
187281
)
188-
raise HTTPException(status_code=400, detail=f"Error creating plan: {e}")
282+
raise HTTPException(status_code=400, detail=f"Error creating plan: {error_msg}") from e
189283

190284

191285
@app.post("/api/human_feedback")
@@ -638,7 +732,11 @@ async def get_plans(
638732

639733
plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps)
640734
plan_with_steps.update_step_counts()
641-
return [plan_with_steps, messages]
735+
736+
# Format dates in messages according to locale
737+
formatted_messages = format_dates_in_messages(messages, config.get_user_local_browser_language())
738+
739+
return [plan_with_steps, formatted_messages]
642740

643741
all_plans = await memory_store.get_all_plans()
644742
# Fetch steps for all plans concurrently

src/backend/kernel_tools/hr_tools.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,25 @@
55
from models.messages_kernel import AgentType
66
import json
77
from typing import get_type_hints
8-
from utils_date import format_date_for_user
8+
from app_config import config
99

1010

1111
class HrTools:
1212
# Define HR tools (functions)
13-
formatting_instructions = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did."
13+
selecetd_language = config.get_user_local_browser_language()
14+
formatting_instructions = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did. Convert all date strings in the following text to short date format with 3-letter month (MMM) in the {selecetd_language} locale (e.g., en-US, en-IN), remove time, and replace original dates with the formatted ones"
1415
agent_name = AgentType.HR.value
1516

1617
@staticmethod
1718
@kernel_function(description="Schedule an orientation session for a new employee.")
1819
async def schedule_orientation_session(employee_name: str, date: str) -> str:
19-
formatted_date = format_date_for_user(date)
2020

2121
return (
2222
f"##### Orientation Session Scheduled\n"
2323
f"**Employee Name:** {employee_name}\n"
24-
f"**Date:** {formatted_date}\n\n"
24+
f"**Date:** {date}\n\n"
2525
f"Your orientation session has been successfully scheduled. "
2626
f"Please mark your calendar and be prepared for an informative session.\n"
27-
f"AGENT SUMMARY: I scheduled the orientation session for {employee_name} on {formatted_date}, as part of her onboarding process.\n"
2827
f"{HrTools.formatting_instructions}"
2928
)
3029

src/backend/kernel_tools/product_tools.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,26 @@
1010
import json
1111
from typing import get_type_hints
1212
from utils_date import format_date_for_user
13+
from app_config import config
1314

1415

1516
class ProductTools:
1617
"""Define Product Agent functions (tools)"""
1718

1819
agent_name = AgentType.PRODUCT.value
20+
selecetd_language = config.get_user_local_browser_language()
1921

2022
@staticmethod
2123
@kernel_function(
22-
description="Add an extras pack/new product to the mobile plan for the customer. For example, adding a roaming plan to their service."
24+
description="Add an extras pack/new product to the mobile plan for the customer. For example, adding a roaming plan to their service. Convert all date strings in the following text to short date format with 3-letter month (MMM) in the {selecetd_language} locale (e.g., en-US, en-IN), remove time, and replace original dates with the formatted ones"
2325
)
2426
async def add_mobile_extras_pack(new_extras_pack_name: str, start_date: str) -> str:
2527
"""Add an extras pack/new product to the mobile plan for the customer. For example, adding a roaming plan to their service. The arguments should include the new_extras_pack_name and the start_date as strings. You must provide the exact plan name, as found using the get_product_info() function."""
2628
formatting_instructions = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did."
27-
formatted_date = format_date_for_user(start_date)
2829
analysis = (
2930
f"# Request to Add Extras Pack to Mobile Plan\n"
3031
f"## New Plan:\n{new_extras_pack_name}\n"
31-
f"## Start Date:\n{formatted_date}\n\n"
32+
f"## Start Date:\n{start_date}\n\n"
3233
f"These changes have been completed and should be reflected in your app in 5-10 minutes."
3334
f"\n\n{formatting_instructions}"
3435
)

src/backend/models/messages_kernel.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ class InputTask(KernelBaseModel):
264264
description: str # Initial goal
265265

266266

267+
class UserLanguage(KernelBaseModel):
268+
language: str
269+
270+
267271
class ApprovalRequest(KernelBaseModel):
268272
"""Message sent to HumanAgent to request approval for a step."""
269273

src/backend/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ azure-ai-evaluation
2323

2424
opentelemetry-exporter-otlp-proto-grpc
2525

26+
# Date and internationalization
27+
babel>=2.9.0
28+
2629
# Testing tools
2730
pytest>=8.2,<9 # Compatible version for pytest-asyncio
2831
pytest-asyncio==0.24.0
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""
2+
Quick test for the fixed utils_date.py functionality
3+
"""
4+
5+
import os
6+
from datetime import datetime
7+
from utils_date import format_date_for_user
8+
9+
10+
def test_date_formatting():
11+
"""Test the date formatting function with various inputs"""
12+
13+
# Set up different language environments
14+
test_cases = [
15+
('en-US', '2025-07-29', 'US English'),
16+
('en-IN', '2025-07-29', 'Indian English'),
17+
('en-GB', '2025-07-29', 'British English'),
18+
('fr-FR', '2025-07-29', 'French'),
19+
('de-DE', '2025-07-29', 'German'),
20+
]
21+
22+
print("Testing date formatting with different locales:")
23+
print("=" * 50)
24+
25+
for locale, date_str, description in test_cases:
26+
os.environ['USER_LOCAL_BROWSER_LANGUAGE'] = locale
27+
try:
28+
result = format_date_for_user(date_str)
29+
print(f"{description} ({locale}): {result}")
30+
except Exception as e:
31+
print(f"{description} ({locale}): ERROR - {e}")
32+
33+
print("\n" + "=" * 50)
34+
print("Testing with datetime object:")
35+
36+
# Test with datetime object
37+
os.environ['USER_LOCAL_BROWSER_LANGUAGE'] = 'en-US'
38+
dt = datetime(2025, 7, 29, 14, 30, 0)
39+
result = format_date_for_user(dt)
40+
print(f"Datetime object: {result}")
41+
42+
print("\nTesting error handling:")
43+
print("=" * 30)
44+
45+
# Test error handling
46+
try:
47+
result = format_date_for_user('invalid-date-string')
48+
print(f"Invalid date: {result}")
49+
except Exception as e:
50+
print(f"Invalid date: ERROR - {e}")
51+
52+
53+
if __name__ == "__main__":
54+
test_date_formatting()

src/backend/tests/test_utils_date_enhanced.py

Whitespace-only changes.

src/frontend/src/api/apiClient.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,8 @@ const fetchWithAuth = async (url: string, method: string = "GET", body: BodyInit
4545
try {
4646
const apiUrl = getApiUrl();
4747
const finalUrl = `${apiUrl}${url}`;
48-
console.log('Final URL:', finalUrl);
49-
console.log('Request Options:', options);
5048
// Log the request details
5149
const response = await fetch(finalUrl, options);
52-
console.log('response', response);
5350

5451
if (!response.ok) {
5552
const errorText = await response.text();
@@ -58,8 +55,6 @@ const fetchWithAuth = async (url: string, method: string = "GET", body: BodyInit
5855

5956
const isJson = response.headers.get('content-type')?.includes('application/json');
6057
const responseData = isJson ? await response.json() : null;
61-
62-
console.log('Response JSON:', responseData);
6358
return responseData;
6459
} catch (error) {
6560
console.info('API Error:', (error as Error).message);
@@ -87,7 +82,6 @@ const fetchWithoutAuth = async (url: string, method: string = "POST", body: Body
8782
const errorText = await response.text();
8883
throw new Error(errorText || 'Login failed');
8984
}
90-
console.log('response', response);
9185
const isJson = response.headers.get('content-type')?.includes('application/json');
9286
return isJson ? await response.json() : null;
9387
} catch (error) {

src/frontend/src/api/apiService.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ const API_ENDPOINTS = {
2121
APPROVE_STEPS: '/approve_step_or_steps',
2222
HUMAN_CLARIFICATION: '/human_clarification_on_plan',
2323
AGENT_MESSAGES: '/agent_messages',
24-
MESSAGES: '/messages'
24+
MESSAGES: '/messages',
25+
USER_BROWSER_LANGUAGE: '/user_browser_language'
2526
};
2627

2728
// Simple cache implementation
@@ -500,6 +501,18 @@ export class APIService {
500501

501502
return Math.round((completedSteps / plan.steps.length) * 100);
502503
}
504+
505+
/**
506+
* Send the user's browser language to the backend
507+
* @returns Promise with response object
508+
*/
509+
async sendUserBrowserLanguage(): Promise<{ status: string }> {
510+
const language = navigator.language || navigator.languages[0] || 'en';
511+
const response = await apiClient.post(API_ENDPOINTS.USER_BROWSER_LANGUAGE, {
512+
language
513+
});
514+
return response;
515+
}
503516
}
504517

505518
// Export a singleton instance

0 commit comments

Comments
 (0)