Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion EfficientAI-Docs/docs/advanced/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Install the required system and Python packages:
sudo apt-get update
sudo apt-get install -y graphviz libgraphviz-dev pkg-config

# Install Python packages
# Install Python packagesdl
Comment thread
TEJASNARAYANS marked this conversation as resolved.
pip install eralchemy graphviz
```

Expand Down
3 changes: 2 additions & 1 deletion app/api/v1/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
voice_playground,
prompt_partials,
prompt_optimization,
telephony,
)

api_router = APIRouter()
Expand Down Expand Up @@ -67,4 +68,4 @@
api_router.include_router(voice_playground.router)
api_router.include_router(prompt_partials.router)
api_router.include_router(prompt_optimization.router)

api_router.include_router(telephony.router)
113 changes: 104 additions & 9 deletions app/api/v1/routes/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from app.models.database import (
Agent, ConversationEvaluation, TestAgentConversation, VoiceBundle,
AIProvider, Integration, IntegrationPlatform, CallMediumEnum,
Evaluator, EvaluatorResult, CallRecording,
Evaluator, EvaluatorResult, CallRecording, TelephonyPhoneNumber,
)
from sqlalchemy import and_
from app.models.schemas import (
Expand Down Expand Up @@ -147,6 +147,38 @@ def generate_unique_agent_id(db: Session) -> str:
)


def _get_telephony_number_for_agent(
db: Session,
organization_id: UUID,
telephony_phone_number_id: Optional[UUID],
current_agent_id: Optional[UUID] = None,
) -> Optional[TelephonyPhoneNumber]:
"""Validate a telephony number can be linked to the agent."""
if not telephony_phone_number_id:
return None

telephony_number = db.query(TelephonyPhoneNumber).filter(
and_(
TelephonyPhoneNumber.id == telephony_phone_number_id,
TelephonyPhoneNumber.organization_id == organization_id,
TelephonyPhoneNumber.is_active == True,
)
).first()
if not telephony_number:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Telephony phone number not found or inactive",
)

if telephony_number.agent_id and telephony_number.agent_id != current_agent_id:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Selected telephony phone number is already assigned to another agent",
)

return telephony_number


def get_agent_dependencies(db: Session, organization_id: UUID, agent_uuid: UUID) -> dict:
"""Return dependency counts that block non-force delete."""
evaluators_count = db.query(Evaluator).filter(
Expand Down Expand Up @@ -196,8 +228,19 @@ async def create_agent(
db: Session = Depends(get_db)
):
"""Create a new test agent"""
selected_telephony_number = None
resolved_phone_number = agent.phone_number
if agent.call_medium == CallMediumEnumSchema.PHONE_CALL:
selected_telephony_number = _get_telephony_number_for_agent(
db=db,
organization_id=organization_id,
telephony_phone_number_id=agent.telephony_phone_number_id,
)
if selected_telephony_number:
resolved_phone_number = selected_telephony_number.phone_number

# Validate phone_number is provided when call_medium is phone_call
if agent.call_medium == CallMediumEnumSchema.PHONE_CALL and not agent.phone_number:
if agent.call_medium == CallMediumEnumSchema.PHONE_CALL and not resolved_phone_number:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="phone_number is required when call_medium is phone_call"
Expand Down Expand Up @@ -253,17 +296,21 @@ async def create_agent(
agent_id=agent_id,
organization_id=organization_id,
name=agent.name,
phone_number=agent.phone_number,
phone_number=resolved_phone_number,
language=agent.language,
description=agent.description,
call_type=agent.call_type,
call_medium=agent.call_medium,
telephony_phone_number_id=selected_telephony_number.id if selected_telephony_number else None,
voice_bundle_id=agent.voice_bundle_id,
ai_provider_id=agent.ai_provider_id,
voice_ai_integration_id=agent.voice_ai_integration_id,
voice_ai_agent_id=agent.voice_ai_agent_id
)
db.add(db_agent)
db.flush()
if selected_telephony_number:
selected_telephony_number.agent_id = db_agent.id
db.commit()
db.refresh(db_agent)

Expand Down Expand Up @@ -353,12 +400,39 @@ async def update_agent(
if not db_agent:
raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")

update_data = agent_update.model_dump(exclude_unset=True, exclude_none=False)
old_telephony_phone_number_id = db_agent.telephony_phone_number_id
next_telephony_phone_number_id = update_data.get(
"telephony_phone_number_id",
old_telephony_phone_number_id,
)
# Determine the call_medium to validate
call_medium = agent_update.call_medium if agent_update.call_medium is not None else db_agent.call_medium
call_medium = (
agent_update.call_medium
if agent_update.call_medium is not None
else db_agent.call_medium
)
call_medium_value = call_medium.value if hasattr(call_medium, "value") else str(call_medium)
selected_telephony_number = None
if call_medium_value == CallMediumEnum.PHONE_CALL.value:
selected_telephony_number = _get_telephony_number_for_agent(
db=db,
organization_id=organization_id,
telephony_phone_number_id=next_telephony_phone_number_id,
current_agent_id=db_agent.id,
)

# Validate phone_number is provided when call_medium is phone_call
if call_medium == CallMediumEnum.PHONE_CALL:
phone_number = agent_update.phone_number if agent_update.phone_number is not None else db_agent.phone_number
if call_medium_value == CallMediumEnum.PHONE_CALL.value:
phone_number = (
selected_telephony_number.phone_number
if selected_telephony_number
else (
agent_update.phone_number
if agent_update.phone_number is not None
else db_agent.phone_number
)
)
if not phone_number:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
Expand Down Expand Up @@ -408,13 +482,34 @@ async def update_agent(
detail="voice_ai_agent_id is required when voice_ai_integration_id is provided"
)

# Convert the update model to dict, handling None values properly
# Use model_dump with exclude_unset to only get fields that were explicitly provided
update_data = agent_update.model_dump(exclude_unset=True, exclude_none=False)
if call_medium_value != CallMediumEnum.PHONE_CALL.value:
update_data["telephony_phone_number_id"] = None

if selected_telephony_number:
update_data["telephony_phone_number_id"] = selected_telephony_number.id
update_data["phone_number"] = selected_telephony_number.phone_number

# Apply updates
for field, value in update_data.items():
setattr(db_agent, field, value)

final_telephony_phone_number_id = update_data.get(
"telephony_phone_number_id",
old_telephony_phone_number_id,
)
if old_telephony_phone_number_id and old_telephony_phone_number_id != final_telephony_phone_number_id:
old_telephony_number = db.query(TelephonyPhoneNumber).filter(
and_(
TelephonyPhoneNumber.id == old_telephony_phone_number_id,
TelephonyPhoneNumber.organization_id == organization_id,
TelephonyPhoneNumber.agent_id == db_agent.id,
)
).first()
if old_telephony_number:
old_telephony_number.agent_id = None

if selected_telephony_number:
selected_telephony_number.agent_id = db_agent.id

db.commit()
db.refresh(db_agent)
Expand Down
30 changes: 28 additions & 2 deletions app/api/v1/routes/integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from datetime import datetime, timezone
from typing import List
from uuid import UUID
from loguru import logger

from app.dependencies import get_db, get_organization_id, get_api_key
from app.models.database import Integration, IntegrationPlatform, Agent
Expand Down Expand Up @@ -114,8 +115,25 @@ async def list_integrations(
integrations = db.query(Integration).filter(
Integration.organization_id == organization_id
).order_by(Integration.created_at.desc()).all()

return integrations

valid_platforms = {p.value for p in IntegrationPlatform}
filtered_integrations: List[Integration] = []
for integration in integrations:
raw_platform = (
integration.platform.value
if hasattr(integration.platform, "value")
else str(integration.platform).lower()
)
if raw_platform in valid_platforms:
filtered_integrations.append(integration)
else:
logger.warning(
"Skipping integration {} with invalid platform '{}'",
integration.id,
integration.platform,
)

return filtered_integrations


@router.get("/{integration_id}", response_model=IntegrationResponse)
Expand All @@ -136,6 +154,14 @@ async def get_integration(

if not integration:
raise HTTPException(status_code=404, detail="Integration not found")

raw_platform = (
integration.platform.value
if hasattr(integration.platform, "value")
else str(integration.platform).lower()
)
if raw_platform not in {p.value for p in IntegrationPlatform}:
raise HTTPException(status_code=404, detail="Integration not found")

return integration

Expand Down
Loading
Loading