Skip to content

Commit 2895f63

Browse files
committed
Add team configuration support and refactor data structure
Introduces team-specific methods to CosmosMemoryContext for managing TeamConfiguration objects, including add, update, retrieve, and delete operations. Refactors JsonService to use the new team methods and ensures unique IDs and timestamps are generated for team configs. Adds a default team JSON, a comprehensive test for team methods, and updates documentation. Also reorganizes data directory structure for consistency.
2 parents 9fe5a5e + b5cc787 commit 2895f63

26 files changed

+475
-127
lines changed

docs/DeploymentGuide.md

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -206,43 +206,9 @@ Once you've opened the project in [Codespaces](#github-codespaces), [Dev Contain
206206

207207
5. Once the deployment has completed successfully, open the [Azure Portal](https://portal.azure.com/), go to the deployed resource group, find the App Service, and get the app URL from `Default domain`.
208208

209-
6. If you are done trying out the application, you can delete the resources by running `azd down`.
209+
6. When Deployment is complete, follow steps in [Set Up Authentication in Azure App Service](../docs/azure_app_service_auth_setup.md) to add app authentication to your web app running on Azure App Service
210210

211-
### Publishing Local Build Container to Azure Container Registry
212-
213-
If you need to rebuild the source code and push the updated container to the deployed Azure Container Registry, follow these steps:
214-
215-
1. Set the environment variable `USE_LOCAL_BUILD` to `True`:
216-
217-
- **Linux/macOS**:
218-
219-
```bash
220-
export USE_LOCAL_BUILD=True
221-
```
222-
223-
- **Windows (PowerShell)**:
224-
```powershell
225-
$env:USE_LOCAL_BUILD = $true
226-
```
227-
228-
2. Run the `az login` command
229-
230-
```bash
231-
az login
232-
```
233-
234-
3. Run the `azd up` command again to rebuild and push the updated container:
235-
```bash
236-
azd up
237-
```
238-
239-
This will rebuild the source code, package it into a container, and push it to the Azure Container Registry associated with your deployment.
240-
241-
This guide provides step-by-step instructions for deploying your application using Azure Container Registry (ACR) and Azure Container Apps.
242-
243-
There are several ways to deploy the solution. You can deploy to run in Azure in one click, or manually, or you can deploy locally.
244-
245-
When Deployment is complete, follow steps in [Set Up Authentication in Azure App Service](../docs/azure_app_service_auth_setup.md) to add app authentication to your web app running on Azure App Service
211+
7. If you are done trying out the application, you can delete the resources by running `azd down`.
246212

247213
# Local setup
248214

src/backend/app_kernel.py

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@
8181
# Add this near the top of your app.py, after initializing the app
8282
app.add_middleware(
8383
CORSMiddleware,
84-
allow_origins=[frontend_url],
84+
allow_origins=[
85+
frontend_url
86+
], # Allow all origins for development; restrict in production
8587
allow_credentials=True,
8688
allow_methods=["*"],
8789
allow_headers=["*"],
@@ -202,8 +204,8 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
202204
"Remove any potentially harmful, inappropriate, or unsafe content",
203205
"Use more professional and constructive language",
204206
"Focus on legitimate business or educational objectives",
205-
"Ensure your request complies with content policies"
206-
]
207+
"Ensure your request complies with content policies",
208+
],
207209
}
208210
authenticated_user = get_authenticated_user_details(request_headers=request.headers)
209211
user_id = authenticated_user["user_principal_id"]
@@ -287,9 +289,7 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
287289
r"Rate limit is exceeded\. Try again in (\d+) seconds?\.", error_msg
288290
)
289291
if match:
290-
error_msg = (
291-
f"Rate limit is exceeded. Try again in {match.group(1)} seconds."
292-
)
292+
error_msg = "Application temporarily unavailable due to quota limits. Please try again later."
293293

294294
track_event_if_configured(
295295
"InputTaskError",
@@ -308,7 +308,7 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
308308
async def create_plan_endpoint(input_task: InputTask, request: Request):
309309
"""
310310
Create a new plan without full processing.
311-
311+
312312
---
313313
tags:
314314
- Plans
@@ -365,19 +365,19 @@ async def create_plan_endpoint(input_task: InputTask, request: Request):
365365
},
366366
)
367367
raise HTTPException(
368-
status_code=400,
368+
status_code=400,
369369
detail={
370370
"error_type": "RAI_VALIDATION_FAILED",
371371
"message": "Content Safety Check Failed",
372372
"description": "Your request contains content that doesn't meet our safety guidelines. Please modify your request to ensure it's appropriate and try again.",
373373
"suggestions": [
374374
"Remove any potentially harmful, inappropriate, or unsafe content",
375-
"Use more professional and constructive language",
375+
"Use more professional and constructive language",
376376
"Focus on legitimate business or educational objectives",
377-
"Ensure your request complies with content policies"
377+
"Ensure your request complies with content policies",
378378
],
379-
"user_action": "Please revise your request and try again"
380-
}
379+
"user_action": "Please revise your request and try again",
380+
},
381381
)
382382

383383
# Get authenticated user
@@ -406,7 +406,7 @@ async def create_plan_endpoint(input_task: InputTask, request: Request):
406406
user_id=user_id,
407407
initial_goal=input_task.description,
408408
overall_status=PlanStatus.in_progress,
409-
source=AgentType.PLANNER.value
409+
source=AgentType.PLANNER.value,
410410
)
411411

412412
# Save the plan to the database
@@ -442,10 +442,12 @@ async def create_plan_endpoint(input_task: InputTask, request: Request):
442442

443443

444444
@app.post("/api/generate_plan")
445-
async def generate_plan_endpoint(generate_plan_request: GeneratePlanRequest, request: Request):
445+
async def generate_plan_endpoint(
446+
generate_plan_request: GeneratePlanRequest, request: Request
447+
):
446448
"""
447449
Generate plan steps for an existing plan using the planner agent.
448-
450+
449451
---
450452
tags:
451453
- Plans
@@ -511,11 +513,17 @@ async def generate_plan_endpoint(generate_plan_request: GeneratePlanRequest, req
511513
kernel, memory_store = await initialize_runtime_and_context("", user_id)
512514

513515
# Get the existing plan
514-
plan = await memory_store.get_plan_by_plan_id(plan_id=generate_plan_request.plan_id)
516+
plan = await memory_store.get_plan_by_plan_id(
517+
plan_id=generate_plan_request.plan_id
518+
)
515519
if not plan:
516520
track_event_if_configured(
517521
"GeneratePlanNotFound",
518-
{"status_code": 404, "detail": "Plan not found", "plan_id": generate_plan_request.plan_id}
522+
{
523+
"status_code": 404,
524+
"detail": "Plan not found",
525+
"plan_id": generate_plan_request.plan_id,
526+
},
519527
)
520528
raise HTTPException(status_code=404, detail="Plan not found")
521529

@@ -538,16 +546,19 @@ async def generate_plan_endpoint(generate_plan_request: GeneratePlanRequest, req
538546

539547
# Create an InputTask from the plan's initial goal
540548
input_task = InputTask(
541-
session_id=plan.session_id,
542-
description=plan.initial_goal
549+
session_id=plan.session_id, description=plan.initial_goal
543550
)
544551

545552
# Use the group chat manager to generate the plan steps
546553
await group_chat_manager.handle_input_task(input_task)
547554

548555
# Get the updated plan with steps
549-
updated_plan = await memory_store.get_plan_by_plan_id(plan_id=generate_plan_request.plan_id)
550-
steps = await memory_store.get_steps_by_plan(plan_id=generate_plan_request.plan_id)
556+
updated_plan = await memory_store.get_plan_by_plan_id(
557+
plan_id=generate_plan_request.plan_id
558+
)
559+
steps = await memory_store.get_steps_by_plan(
560+
plan_id=generate_plan_request.plan_id
561+
)
551562

552563
# Log successful plan generation
553564
track_event_if_configured(
@@ -760,7 +771,7 @@ async def human_clarification_endpoint(
760771
},
761772
)
762773
raise HTTPException(
763-
status_code=400,
774+
status_code=400,
764775
detail={
765776
"error_type": "RAI_VALIDATION_FAILED",
766777
"message": "Clarification Safety Check Failed",
@@ -769,10 +780,10 @@ async def human_clarification_endpoint(
769780
"Use clear and professional language",
770781
"Avoid potentially harmful or inappropriate content",
771782
"Focus on providing constructive feedback or clarification",
772-
"Ensure your message complies with content policies"
783+
"Ensure your message complies with content policies",
773784
],
774-
"user_action": "Please revise your clarification and try again"
775-
}
785+
"user_action": "Please revise your clarification and try again",
786+
},
776787
)
777788

778789
authenticated_user = get_authenticated_user_details(request_headers=request.headers)

src/backend/context/cosmos_memory_kernel.py

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@
1717

1818
# Import the AppConfig instance
1919
from app_config import config
20-
from models.messages_kernel import BaseDataModel, Plan, Session, Step, AgentMessage
20+
from models.messages_kernel import (
21+
BaseDataModel,
22+
Plan,
23+
Session,
24+
Step,
25+
AgentMessage,
26+
TeamConfiguration,
27+
)
2128

2229

2330
# Add custom JSON encoder class for datetime objects
@@ -38,6 +45,7 @@ class CosmosMemoryContext(MemoryStoreBase):
3845
"plan": Plan,
3946
"step": Step,
4047
"agent_message": AgentMessage,
48+
"team_config": TeamConfiguration,
4149
# Messages are handled separately
4250
}
4351

@@ -341,6 +349,131 @@ async def get_agent_messages_by_session(
341349
messages = await self.query_items(query, parameters, AgentMessage)
342350
return messages
343351

352+
async def add_team(self, team: TeamConfiguration) -> None:
353+
"""Add a team configuration to Cosmos DB.
354+
355+
Args:
356+
team: The TeamConfiguration to add
357+
"""
358+
await self.add_item(team)
359+
360+
async def update_team(self, team: TeamConfiguration) -> None:
361+
"""Update an existing team configuration in Cosmos DB.
362+
363+
Args:
364+
team: The TeamConfiguration to update
365+
"""
366+
await self.update_item(team)
367+
368+
async def get_team(self, team_id: str) -> Optional[TeamConfiguration]:
369+
"""Retrieve a specific team configuration by team_id.
370+
371+
Args:
372+
team_id: The team_id of the team configuration to retrieve
373+
374+
Returns:
375+
TeamConfiguration object or None if not found
376+
"""
377+
query = "SELECT * FROM c WHERE c.team_id=@team_id AND c.data_type=@data_type"
378+
parameters = [
379+
{"name": "@team_id", "value": team_id},
380+
{"name": "@data_type", "value": "team_config"},
381+
]
382+
teams = await self.query_items(query, parameters, TeamConfiguration)
383+
return teams[0] if teams else None
384+
385+
async def get_team_by_id(self, id: str) -> Optional[TeamConfiguration]:
386+
"""Retrieve a specific team configuration by its document id.
387+
388+
Args:
389+
id: The document id of the team configuration to retrieve
390+
391+
Returns:
392+
TeamConfiguration object or None if not found
393+
"""
394+
query = "SELECT * FROM c WHERE c.id=@id AND c.data_type=@data_type"
395+
parameters = [
396+
{"name": "@id", "value": id},
397+
{"name": "@data_type", "value": "team_config"},
398+
]
399+
teams = await self.query_items(query, parameters, TeamConfiguration)
400+
return teams[0] if teams else None
401+
402+
async def get_all_teams_by_user(self, user_id: str) -> List[TeamConfiguration]:
403+
"""Retrieve all team configurations for a specific user.
404+
405+
Args:
406+
user_id: The user_id to get team configurations for
407+
408+
Returns:
409+
List of TeamConfiguration objects
410+
"""
411+
query = "SELECT * FROM c WHERE c.user_id=@user_id AND c.data_type=@data_type ORDER BY c.created DESC"
412+
parameters = [
413+
{"name": "@user_id", "value": user_id},
414+
{"name": "@data_type", "value": "team_config"},
415+
]
416+
teams = await self.query_items(query, parameters, TeamConfiguration)
417+
return teams
418+
419+
async def delete_team(self, team_id: str) -> bool:
420+
"""Delete a team configuration by team_id.
421+
422+
Args:
423+
team_id: The team_id of the team configuration to delete
424+
425+
Returns:
426+
True if team was found and deleted, False otherwise
427+
"""
428+
await self.ensure_initialized()
429+
430+
try:
431+
# First find the team to get its document id and partition key
432+
team = await self.get_team(team_id)
433+
if team:
434+
# Use the session_id as partition key, or fall back to user_id if no session_id
435+
partition_key = (
436+
team.session_id
437+
if hasattr(team, "session_id") and team.session_id
438+
else team.user_id
439+
)
440+
await self._container.delete_item(
441+
item=team.id, partition_key=partition_key
442+
)
443+
return True
444+
return False
445+
except Exception as e:
446+
logging.exception(f"Failed to delete team from Cosmos DB: {e}")
447+
return False
448+
449+
async def delete_team_by_id(self, id: str) -> bool:
450+
"""Delete a team configuration by its document id.
451+
452+
Args:
453+
id: The document id of the team configuration to delete
454+
455+
Returns:
456+
True if team was found and deleted, False otherwise
457+
"""
458+
await self.ensure_initialized()
459+
460+
try:
461+
# First find the team to get its partition key
462+
team = await self.get_team_by_id(id)
463+
if team:
464+
# Use the session_id as partition key, or fall back to user_id if no session_id
465+
partition_key = (
466+
team.session_id
467+
if hasattr(team, "session_id") and team.session_id
468+
else team.user_id
469+
)
470+
await self._container.delete_item(item=id, partition_key=partition_key)
471+
return True
472+
return False
473+
except Exception as e:
474+
logging.exception(f"Failed to delete team from Cosmos DB: {e}")
475+
return False
476+
344477
async def add_message(self, message: ChatMessageContent) -> None:
345478
"""Add a message to the memory and save to Cosmos DB."""
346479
await self.ensure_initialized()

0 commit comments

Comments
 (0)