Skip to content

Commit 7b15649

Browse files
RAI validation
1 parent 2c79c57 commit 7b15649

File tree

5 files changed

+158
-4
lines changed

5 files changed

+158
-4
lines changed

src/backend/app_kernel.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
from services.json_service import JsonService
4343

4444
# Updated import for KernelArguments
45-
from utils_kernel import initialize_runtime_and_context, rai_success
45+
from utils_kernel import initialize_runtime_and_context, rai_success, rai_validate_team_config
4646

4747

4848
# Check if the Application Insights Instrumentation Key is set in the environment variables
@@ -1476,6 +1476,34 @@ async def upload_team_config_endpoint(request: Request, file: UploadFile = File(
14761476
status_code=400, detail=f"Invalid JSON format: {str(e)}"
14771477
)
14781478

1479+
# Validate content with RAI before processing
1480+
rai_valid, rai_error = await rai_validate_team_config(json_data)
1481+
if not rai_valid:
1482+
# Track RAI validation failure
1483+
track_event_if_configured(
1484+
"Team configuration RAI validation failed",
1485+
{
1486+
"status": "failed",
1487+
"user_id": user_id,
1488+
"filename": file.filename,
1489+
"reason": rai_error,
1490+
},
1491+
)
1492+
raise HTTPException(
1493+
status_code=400,
1494+
detail=rai_error
1495+
)
1496+
1497+
# Track successful RAI validation
1498+
track_event_if_configured(
1499+
"Team configuration RAI validation passed",
1500+
{
1501+
"status": "passed",
1502+
"user_id": user_id,
1503+
"filename": file.filename,
1504+
},
1505+
)
1506+
14791507
# Initialize memory store and service
14801508
kernel, memory_store = await initialize_runtime_and_context("", user_id)
14811509
json_service = JsonService(memory_store)

src/backend/utils_kernel.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,68 @@ async def rai_success(description: str, is_task_creation: bool) -> bool:
293293
logging.error(f"Error in RAI check: {str(e)}")
294294
# Default to blocking the operation if RAI check fails for safety
295295
return False
296+
297+
298+
async def rai_validate_team_config(team_config_json: dict) -> tuple[bool, str]:
299+
"""
300+
Validates team configuration JSON content for RAI compliance.
301+
302+
Args:
303+
team_config_json: The team configuration JSON data to validate
304+
305+
Returns:
306+
Tuple of (is_valid, error_message)
307+
- is_valid: True if content passes RAI checks, False otherwise
308+
- error_message: Simple error message if validation fails
309+
"""
310+
try:
311+
# Extract all text content from the team configuration
312+
text_content = []
313+
314+
# Extract team name and description
315+
if "name" in team_config_json:
316+
text_content.append(team_config_json["name"])
317+
if "description" in team_config_json:
318+
text_content.append(team_config_json["description"])
319+
320+
# Extract agent information
321+
if "agents" in team_config_json:
322+
for agent in team_config_json["agents"]:
323+
if isinstance(agent, dict):
324+
if "name" in agent:
325+
text_content.append(agent["name"])
326+
if "description" in agent:
327+
text_content.append(agent["description"])
328+
if "role" in agent:
329+
text_content.append(agent["role"])
330+
if "capabilities" in agent and isinstance(agent["capabilities"], list):
331+
text_content.extend(agent["capabilities"])
332+
333+
# Extract starting tasks
334+
if "starting_tasks" in team_config_json:
335+
for task in team_config_json["starting_tasks"]:
336+
if isinstance(task, str):
337+
text_content.append(task)
338+
elif isinstance(task, dict):
339+
if "name" in task:
340+
text_content.append(task["name"])
341+
if "prompt" in task:
342+
text_content.append(task["prompt"])
343+
344+
# Combine all text content for validation
345+
combined_content = " ".join(text_content)
346+
347+
if not combined_content.strip():
348+
return False, "Team configuration contains no readable text content"
349+
350+
# Use existing RAI validation function
351+
rai_result = await rai_success(combined_content, False)
352+
353+
if not rai_result:
354+
return False, "Team configuration contains inappropriate content and cannot be uploaded."
355+
356+
return True, ""
357+
358+
except Exception as e:
359+
logging.error(f"Error validating team configuration with RAI: {str(e)}")
360+
return False, "Unable to validate team configuration content. Please try again."

src/frontend/src/components/common/SettingsButton.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const SettingsButton: React.FC<SettingsButtonProps> = ({
4242
const [loading, setLoading] = useState(false);
4343
const [uploadLoading, setUploadLoading] = useState(false);
4444
const [error, setError] = useState<string | null>(null);
45+
const [uploadMessage, setUploadMessage] = useState<string | null>(null);
4546
const [tempSelectedTeam, setTempSelectedTeam] = useState<TeamConfig | null>(null);
4647

4748
const loadTeams = async () => {
@@ -67,8 +68,12 @@ const SettingsButton: React.FC<SettingsButtonProps> = ({
6768
if (open) {
6869
await loadTeams();
6970
setTempSelectedTeam(selectedTeam || null);
71+
setError(null);
72+
setUploadMessage(null);
7073
} else {
7174
setTempSelectedTeam(null);
75+
setError(null);
76+
setUploadMessage(null);
7277
}
7378
};
7479

@@ -95,27 +100,37 @@ const SettingsButton: React.FC<SettingsButtonProps> = ({
95100
console.log('SettingsButton: Starting file upload for:', file.name);
96101
setUploadLoading(true);
97102
setError(null);
103+
setUploadMessage('Uploading and validating team configuration...');
98104

99105
try {
100106
const result = await TeamService.uploadCustomTeam(file);
101107
console.log('SettingsButton: Upload result:', result);
108+
102109
if (result.success) {
103110
console.log('SettingsButton: Upload successful, reloading teams...');
111+
setUploadMessage('Team uploaded successfully! Refreshing teams...');
104112
// Reload teams to include the new custom team
105113
await loadTeams();
106114
console.log('SettingsButton: Teams reloaded after upload');
115+
setUploadMessage(null);
107116
// Notify parent component about the upload
108117
if (onTeamUpload) {
109118
console.log('SettingsButton: Notifying parent about upload...');
110119
await onTeamUpload();
111120
}
121+
} else if (result.raiError) {
122+
console.error('SettingsButton: Upload failed due to RAI validation:', result.raiError);
123+
setError('Upload failed: Team configuration contains inappropriate content.');
124+
setUploadMessage(null);
112125
} else {
113126
console.error('SettingsButton: Upload failed:', result.error);
114127
setError(result.error || 'Failed to upload team configuration');
128+
setUploadMessage(null);
115129
}
116130
} catch (err: any) {
117131
console.error('SettingsButton: Upload exception:', err);
118132
setError(err.message || 'Failed to upload team configuration');
133+
setUploadMessage(null);
119134
} finally {
120135
setUploadLoading(false);
121136
// Reset the input
@@ -308,11 +323,35 @@ const SettingsButton: React.FC<SettingsButtonProps> = ({
308323
<DialogContent style={{ width: '100%', padding: '0' }}>
309324
<DialogBody style={{ width: '100%', padding: '16px 24px', display: 'block' }}>
310325
{error && (
311-
<div style={{ color: 'red', marginBottom: '16px' }}>
326+
<div style={{
327+
color: '#d13438',
328+
marginBottom: '16px',
329+
padding: '12px',
330+
backgroundColor: '#fdf2f2',
331+
border: '1px solid #f5c6cb',
332+
borderRadius: '4px'
333+
}}>
312334
<Text>{error}</Text>
313335
</div>
314336
)}
315337

338+
{uploadMessage && (
339+
<div style={{
340+
color: '#107c10',
341+
marginBottom: '16px',
342+
padding: '12px',
343+
backgroundColor: '#f3f9f3',
344+
border: '1px solid #c6e7c6',
345+
borderRadius: '4px',
346+
display: 'flex',
347+
alignItems: 'center',
348+
gap: '8px'
349+
}}>
350+
<Spinner size="extra-small" />
351+
<Text>{uploadMessage}</Text>
352+
</div>
353+
)}
354+
316355
{loading ? (
317356
<div style={{ display: 'flex', justifyContent: 'center', padding: '32px' }}>
318357
<Spinner label="Loading teams..." />

src/frontend/src/services/TeamService.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export class TeamService {
55
/**
66
* Upload a custom team configuration
77
*/
8-
static async uploadCustomTeam(teamFile: File): Promise<{ success: boolean; team?: TeamConfig; error?: string }> {
8+
static async uploadCustomTeam(teamFile: File): Promise<{ success: boolean; team?: TeamConfig; error?: string; raiError?: any }> {
99
try {
1010
console.log('TeamService: Starting team upload for file:', teamFile.name);
1111
const formData = new FormData();
@@ -23,9 +23,31 @@ export class TeamService {
2323
} catch (error: any) {
2424
console.error('TeamService: Upload failed:', error);
2525
console.error('TeamService: Upload error details:', error.response?.data);
26+
27+
// Check if this is an RAI validation error
28+
const errorDetail = error.response?.data?.detail || error.response?.data;
29+
30+
// If the error message contains "inappropriate content", treat it as RAI error
31+
if (typeof errorDetail === 'string' && errorDetail.includes('inappropriate content')) {
32+
return {
33+
success: false,
34+
raiError: {
35+
error_type: 'RAI_VALIDATION_FAILED',
36+
message: errorDetail,
37+
description: errorDetail
38+
}
39+
};
40+
}
41+
42+
// Get error message from the response
43+
let errorMessage = error.message || 'Failed to upload team configuration';
44+
if (error.response?.data?.detail) {
45+
errorMessage = error.response.data.detail;
46+
}
47+
2648
return {
2749
success: false,
28-
error: error.message || 'Failed to upload team configuration'
50+
error: errorMessage
2951
};
3052
}
3153
}

test-rai-failure.json

Whitespace-only changes.

0 commit comments

Comments
 (0)