# **Kɑlimerɑ.ɑi [Backend]**
- Created at : 2025-03-31
- Created by : Manoj Vala
## **Introduction**
This document outlines the process of:
1. Creating an **AI Agent** and storing its configuration in the database.
2. Implementing **API endpoints for conversation statistics**, dynamically calculated from real conversations with agents.
>[!IMPORTANT]
>```python
>api.add_resource(kalimera, 'kalimera/')
>```
>All the methods and endpoints are called with prefix `kalimera`.
## **Part 1: AI Agent API**
### **Step 1: Define the AI Agent Model**
**Description:** To store the AI Agent configuration, we will create a database model where predefined fields will be stored separately, and the rest of the configuration will be stored in a `JSONB` column.
---
### **AI Agent Database Schema**
| **Field Name** | **Type** | **Attributes** | **Description** |
|-------------|-----------|-------------|--------------|
| `id` | `UUID` | `primary_key=True` | Unique identifier for the AI Agent. |
| `tenant_id` | `UUID` | `nullable=False` | For mapping of user. |
| `name` | `String(255)` | `nullable=False` | Name of the AI Agent (required). |
| `profile_type` | `String(50)` | `nullable=False` | Choice ['inbount', 'outbound'] |
| `configuration` | `JSONB` | `nullable=True` | Includes all the section |
| created_at | DateTime | server_default=db.func.now() | Timestamp of when the agent was created (auto-set).|
| updated_at | DateTime | onupdate=db.func.now() | Timestamp of the last update (auto-updated).|
---
### **Key Notes**
- **`JSONB`**: PostgreSQL’s binary JSON format for flexible, schema-less storage.
- **Timestamps**: `created_at` and `updated_at` are automated.
**Code Snippet:**
```python
class KalimeraAIAgent(db.Model):
id = db.Column(db.UUID, primary_key=True)
tenant_id = db.Column(db.UUID, nullable=False)
name = db.Column(db.String(255), nullable=False)
profile_type = db.Column(db.String(255), nullable=True)
configuration = db.Column(JSONB, nullable=True)
created_at = db.Column(db.DateTime, server_default=db.func.now())
updated_at = db.Column(db.DateTime, onupdate=db.func.now())
```
---
### **Step 2: Create the AI Agent API Endpoint**
**Description:** We will create a RESTful API using Flask-RESTful with three methods. All the database transactions are made in a seperate **Service** file. AI Agent are stored in database as well as in redis cache.:
- `GET`: Retrieve an AI Agent by ID. Some data will be **hidden**. it cannot be readable for anyone.
- `POST`: Create a new AI Agent.
- `PUT`: Update an existing AI Agent.
- `DELETE`: Delete an existing AI Agent.
**Code Snippet:**
```python
class ConfigurationSection(Enum):
AI_HUB = "AIHub"
AUDIO_SERVER = "AudioServer"
AZURE = "Azure"
ELEVEN_LABS_TTS = "ElevenLabsTTS"
CALL_PROCESSING = "CallProcessing"
REDIS = "Redis"
KNOWLEDGE_BASE = "KnowledgeBase"
BACKGROUND_AUDIO = "BackgroundAudio"
WEB_TALK = "WebTalk"
EXPERIMENTAL_FEATURES = "ExperimentalFeatures"
SERILOG = "Serilog"
LOGGING = "Logging"
ALLOWED_HOSTS = "AllowedHosts"
class KalimeraAgent(Resource):
def get(self, app_model, end_user):
"""
Get the agent profile by agent ID.\n
:param agent_id: The ID of the agent.\n
:return: The agent profile.
"""
if agent_id:
agent = db.session.query(KalimeraAIAgent).filter(
KalimeraAIAgent.tenant_id == tenant_id,
KalimeraAIAgent.id == agent_id).first()
if agent:
return agent
elif tenant_id and isinstance(args, dict):
search_value = args.get("search", "")
if search_value:
search_value = search_value.strip()
agents = db.session.query(KalimeraAIAgent).filter(
KalimeraAIAgent.tenant_id == tenant_id,
or_(
KalimeraAIAgent.name.ilike(f"%{search_value}%"),
KalimeraAIAgent.description.ilike(f"%{search_value}%"),
KalimeraAIAgent.profile_type.ilike(f"%{search_value}%")
)
).order_by(
KalimeraAIAgent.created_at.desc()
).limit(args.get("limit", 10)).offset((args.get("page", 1) - 1) * args.get("limit", 10)).all()
else:
agents = db.session.query(KalimeraAIAgent).filter(
KalimeraAIAgent.tenant_id == tenant_id
).order_by(
KalimeraAIAgent.created_at.desc()
).limit(args.get("limit", 10)).offset((args.get("page", 1) - 1) * args.get("limit", 10)).all()
if agents:
return agents
return False
def post(self, app_model, end_user):
"""Create a new AI Agent with the provided data."""
if "avatar" in args and not cls.is_valid_image_url(args["avatar"]):
return False, "Invalid image URL.", 400, None
if args["type"] not in ["inbound", "outbound"]:
return False, "Invalid profile type.", 400, None
agent = KalimeraAIAgent(
name = args["name"],
description = args["description"],
avatar = args["avatar"] if "avatar" in args else None,
profile_type = args["type"],
tenant_id = tenant_id
)
db.session.add(agent)
db.session.commit()
db.session.refresh(agent)
agent_id = agent.id
db.session.close()
return True, "Agent created successfully", 201, str(agent_id)
def put(self, app_model, end_user):
try:
ConfigurationSection(section)
agent = KalimeraAIAgent.query.get(agent_id)
if not agent:
return jsonify({'error': 'AI Agent not found'}), 404
try:
new_data = request.json
updated_config = update_configuration_section(agent, section, new_data)
return jsonify({'message': 'Configuration updated successfully', 'configuration': updated_config}), 200
except Exception as e:
return jsonify({'error': str(e)}), 400
except Exception as e:
return jsonify({'error': 'Invalid section name'}), 404
def delete(self, app_model, end_user):
agent = KalimeraAIAgent.query.get_or_404(agent_id)
db.session.delete(agent)
db.session.commit()
return {'message': 'Contact deleted successfully'}
api.add_resource(KalimeraAgent, 'kalimera/ai-agent', 'kalimera/ai-agent/<int:agent_id>', 'kalimera/ai-agent/<uuid:agent_id>/<section>')
```
## Update Agent configuration
```python
class UpdateKalimeraAgent(Resource):
def update_configuration_section(agent, section_name, new_data):
if not agent.configuration:
agent.configuration = {}
agent.configuration[section_name] = new_data
db.session.commit()
return agent.configuration
def put(agent_id, section):
if isinstance(configuration, dict):
agent = db.session.query(KalimeraAIAgent).filter(
KalimeraAIAgent.tenant_id == agent_id,
KalimeraAIAgent.id == agent_id).first()
if not agent:
db.session.close()
return False, "Agent profile not found.", 404
# Update the configuration section
if not agent.configuration:
agent.configuration = configuration
else:
agent.configuration.update(configuration)
db.session.commit()
db.session.refresh(agent)
db.session.close()
return True, "Configuration section updated successfully.", 200
api.add_resource(UpdateKalimeraAgent, 'kalimera/ai-agent/<uuid:agent_id>')
```
---
### **Step 3: Handle API Endpoint in service file**
**Description:** Every endpoints will call service file `KalimeraService`. Which includes whole logic of controller.
**Service** file. AI Agent are stored in database as well as in redis cache.:
## 📌 **1. Retrieve AI Agent**
>
> **Endpoint**
> ```
> /ai-agent/{agent_id}
> ```
>
> **Description**:
> Retrieve an AI Agent by ID. Some data will be **hidden**. it cannot be readable for anyone.
>
> ### **Method**
> `GET`
>
> ### 🔑 **Headers**
> | Header | Type | Required | Description |
> |----------------|--------|----------|-------------------------------|
> | `Content-Type` | String | ✅ | `application/json` |
> | `Authorization`| String | ✅ | `api-token-generated` |
>
> **cURL Example**
> ```bash
> curl --location 'https://api.my-buddy.ai/console/api/ai-agent/<uuid>' \
> --header 'Content-Type: application/json'
> ```
> **Response**
> ```json
> { "id": 1, "name": "AI Assistant", "description": "Virtual AI Agent", "ai_hub": {}, "audio_server": {}, "azure": {}, "eleven_labs_tts": {}, "call_processing": {}, "redis": {}, "knowledge_base": {}, "background_audio": {}, "web_talk": {}, "experimental_features": {}, "serilog": {}, "logging": {}, "allowed_hosts": {}}
> ```
>
> Service file:
> ```python
> class AIAgentService:
> @staticmethod
> def get_ai_agent(agent_id):
> """Fetch AI Agent from Redis or Database."""
> cache_key = f"kalimera:{agent_id}"
> cached_agent = redis_client.get(cache_key)
>
> if cached_agent:
> return json.loads(cached_agent) # Return cached data if available
>
> agent = KalimeraAIAgent.query.get(agent_id)
> if not agent:
> return None # Return None if agent not found
>
> agent_data = {
> "id": agent.id,
> "name": agent.name,
> "description": agent.description,
> "ai_hub": agent.ai_hub,
> "audio_server": agent.audio_server,
> "azure": agent.azure,
> "eleven_labs_tts": agent.eleven_labs_tts,
> "call_processing": agent.call_processing,
> "redis": agent.redis,
> "knowledge_base": agent.knowledge_base,
> "background_audio": agent.background_audio,
> "web_talk": agent.web_talk,
> "experimental_features": agent.experimental_features,
> "serilog": agent.serilog,
> "logging": agent.logging,
> "allowed_hosts": agent.allowed_hosts
> }
>
> redis_client.setex(cache_key, 3600, json.dumps(agent_data)) # Store in Redis for 1 hour
> return agent_data
>```
---
### 📌 **2. Create AI Agent**
>
> **Endpoint**
> ```
> /ai-agent
> ```
>
> **Description**:
> Creates a new AI Agent and stores it in the database. The agent data is also cached in Redis.
>
> ### **Method**
> `POST`
>
> ### 🔑 **Headers**
> | Header | Type | Required | Description |
> |---------------|--------|----------|-------------------------------|
> | `Content-Type` | String | ✅ | `application/json` |
> | `Authorization` | String | ✅ | `api-token-generated` |
>
> ### **Request Body**
> ```json
> { "name": "AI Assistant", "description": "Virtual AI Agent", "ai_hub": {}, "audio_server": {}, "azure": {}, "eleven_labs_tts": {}, "call_processing": {}, "redis": {}, "knowledge_base": {}, "background_audio": {}, "web_talk": {}, "experimental_features": {}, "serilog": {}, "logging": {}, "allowed_hosts": {}}
> ```
>
> ### **cURL Example**
> ```bash
> curl --location 'https://api.my-buddy.ai/console/api/ai-agent' \
> --header 'Content-Type: application/json' \
> --data '{ "name": "AI Assistant", "description": "Virtual AI Agent", "ai_hub": {}, "audio_server": {}, "azure": {}, "eleven_labs_tts": {}, "call_processing": {}, "redis": {}, "knowledge_base": {}, "background_audio": {}, "web_talk": {}, "experimental_features": {}, "serilog": {}, "logging": {}, "allowed_hosts": {}}'
> ```
>
> ### **Response**
> ```json
> {
> "id": 1,
> "message": "AI Agent created successfully"
> }
> ```
>
---
>
> ### **Service File**
> ```python
> class AIAgentService:
> @staticmethod
> def create_ai_agent(agent_data):
> """Create a new AI Agent, store it in DB, and cache it in Redis."""
> new_agent = KalimeraAIAgent(**agent_data)
> db.session.add(new_agent)
> db.session.commit()
>
> agent_data["id"] = new_agent.id
> cache_key = f"kalimera:{new_agent.id}"
> redis_client.setex(cache_key, 3600, json.dumps(agent_data)) # Cache in Redis for 1 hour
>
> return {"id": new_agent.id, "name": new_agent.name, "description": new_agent.description, "message": "AI Agent created successfully"}
> ```
>
---
## 📌 **3. Update AI Agent**
>
> **Endpoint**
> ```
> /ai-agent/{agent_id}
> ```
>
> **Description**:
> Updates an existing AI Agent's details. The updated data is stored in the database and refreshed in Redis.
>
> ### **Method**
> `PUT`
>
> ### 🔑 **Headers**
> | Header | Type | Required | Description |
> |---------------|--------|----------|-------------------------------|
> | `Content-Type` | String | ✅ | `application/json` |
> | `Authorization` | String | ✅ | `api-token-generated` |
>
> ### **Request Body**
> ```json
> { "name": "Updated AI Assistant", "description": "Updated Virtual AI Agent", "ai_hub": {}, "audio_server": {}, "azure": {}, "eleven_labs_tts": {}, "call_processing": {}, "redis": {}, "knowledge_base": {}, "background_audio": {}, "web_talk": {}, "experimental_features": {}, "serilog": {}, "logging": {}, "allowed_hosts": {}}
> ```
>
> ### **cURL Example**
> ```bash
> curl --location --request PUT 'https://api.my-buddy.ai/console/api/ai-agent/<uuid>' \
> --header 'Content-Type: application/json' \
> --data '{ "name": "Updated AI Assistant", "description": "Updated Virtual AI Agent", "ai_hub": {}, "audio_server": {}, "azure": {}, "eleven_labs_tts": {}, "call_processing": {}, "redis": {}, "knowledge_base": {}, "background_audio": {}, "web_talk": {}, "experimental_features": {}, "serilog": {}, "logging": {}, "allowed_hosts": {}}'
> ```
>
> ### **Response**
> ```json
> {
> "id": 1,
> "message": "AI Agent updated successfully"
> }
> ```
>
> ---
>
> ### **Service File**
> ```python
> class AIAgentService:
> @staticmethod
> def update_ai_agent(agent_id, agent_data):
> """Update an existing AI Agent, store it in DB, and refresh cache."""
> agent = KalimeraAIAgent.query.get(agent_id)
> if not agent:
> return None # Return None if agent does not exist
>
> for key, value in agent_data.items():
> setattr(agent, key, value) # Update agent attributes
>
> agent_data["id"] = agent_id
> cache_key = f"kalimera:{agent_id}"
> redis_client.setex(cache_key, 3600, json.dumps(agent_data)) # Refresh cache
>
> return {"id": agent_id, "message": "AI Agent updated successfully"}
> ```
---
## **Part 2: Voice APIs**
## 1. Contact Management:
| Field | Type | Nullable | Description |
|--------|------|----------|---------------|
| id | Integer | False| Primary key |
| first_name | String(50)| False| Contact's first name (required)|
| last_name | String(50) | False| Contact's last name (required)|
| contact_no | String(20) | True| Contact phone number |
| email | String(100) | True| Contact email address|
| start_datetime | String(50) | True| Start date/time of interaction|
| end_datetime | String(50) | True| End date/time of interaction |
| language| String(30) | True| Language preference (e.g., English) |
| voice | String(10) | True| Voice preference ('Male'/'Female') |
| status | String(20) | True| Interaction status ('Inbound'/'Outbound') |
```python
class KalimeraContact(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(50), nullable=False)
last_name = db.Column(db.String(50), nullable=False)
contact_no = db.Column(db.String(20))
email = db.Column(db.String(100))
start_datetime = db.Column(db.String(50))
end_datetime = db.Column(db.String(50))
language = db.Column(db.String(30))
voice = db.Column(db.String(10)) # 'Male' or 'Female'
status = db.Column(db.String(20)) # 'Inbound' or 'Outbound'
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
def to_dict(self):
return { 'id': self.id, 'first_name': self.first_name, 'last_name': self.last_name, 'contact_no': self.contact_no, 'email': self.email, 'start_datetime': self.start_datetime, 'end_datetime': self.end_datetime, 'language': self.language, 'voice': self.voice, 'status': self.status}
```
---
### To manage above contact table we need controller which is as below:
```python
# RESTful Resource
class KalimeraContact(Resource):
def get(self, contact_id=None):
if contact_id:
contact = Contact.query.get_or_404(contact_id)
return contact.to_dict()
else:
# Pagination parameters
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
contacts = Contact.query.paginate(page=page, per_page=per_page)
return {
'contacts': [contact.to_dict() for contact in contacts.items],
'total': contacts.total,
'pages': contacts.pages,
'current_page': contacts.page
}
def post(self):
data = request.get_json()
# Validate required fields
if not data.get('first_name') or not data.get('last_name'):
return {'message': 'First name and last name are required'}, 400
contact = KalimeraContact(
first_name=data['first_name'],
last_name=data['last_name'],
contact_no=data.get('contact_no'),
email=data.get('email'),
start_datetime=data.get('start_datetime', '24 Aug, 05:30 pm'), # Default from image
end_datetime=data.get('end_datetime', '24 Aug, 05:30 pm'), # Default from image
language=data.get('language', 'English'), # Default from image
voice=data.get('voice', 'Male'), # Default from image
status=data.get('status', 'Inbound') # Default from image
)
db.session.add(contact)
db.session.commit()
return contact.to_dict(), 201
def put(self, contact_id):
contact = KalimeraContact.query.get_or_404(contact_id)
data = request.get_json()
# Update fields if they exist in the request
if 'first_name' in data:
contact.first_name = data['first_name']
if 'last_name' in data:
contact.last_name = data['last_name']
if 'contact_no' in data:
contact.contact_no = data['contact_no']
if 'email' in data:
contact.email = data['email']
if 'start_datetime' in data:
contact.start_datetime = data['start_datetime']
if 'end_datetime' in data:
contact.end_datetime = data['end_datetime']
if 'language' in data:
contact.language = data['language']
if 'voice' in data:
contact.voice = data['voice']
if 'status' in data:
contact.status = data['status']
db.session.commit()
return contact.to_dict()
def delete(self, contact_id):
contact = Contact.query.get_or_404(contact_id)
db.session.delete(contact)
db.session.commit()
return {'message': 'Contact deleted successfully'}
api.add_resource(KalimeraContact, '/kalimera/contact', '/kalimera/contact/<int:contact_id>')
```
### Note:
> Above endpoint will manage whole contacts by create, update, read and delete methods.
**For storing conversation records need to create table `KalimeraConversation`**
### Schema:
Here is the updated **table view** for the `KalimeraConversation` model based on the latest fields:
| **Column Name** | **Data Type**| **Constraints** | **Description** |
|-----------------|--------------|---------------------|----------------|
| `conversation_guid` | UUID | Primary Key, Default: `uuid_generate_v4()` | Unique identifier for the conversation. |
| `agent_id`| UUID | Not Null | Identifier for the agent handling the conversation. |
| `tenant_id` | UUID | Not Null | Identifier for the tenant. |
| `title` | VARCHAR(255) | Not Null | Title of the conversation. |
| `status` | VARCHAR(255) | Not Null | Status of the conversation (Active, Completed, Failed, etc.). |
| `profile_type` | ENUM(`inbound`, `outbound`) | Not Null | Defines whether the conversation is **inbound** or **outbound**. |
| `instance_id` | UUID | Nullable | Identifier of the user who initiated the conversation. |
| `from_number` | VARCHAR(255) | Nullable | When agent receive the call |
| `to_number` | VARCHAR(255) | Nullable | When agent calls |
| `created_at` | TIMESTAMP | Not Null, Default: `CURRENT_TIMESTAMP(0)` | Creation timestamp. |
| `updated_at` | TIMESTAMP | Not Null, Default: `CURRENT_TIMESTAMP(0)` | Last updated timestamp. |
| `input_file_url` | TEXT| Nullable | URL of the conversation's associated file in Azure Blob Storage. |
| `output_file_url` | TEXT| Nullable | URL of the conversation's associated file in Azure Blob Storage. |
| `conversation_url` | TEXT| Nullable | URL of the conversation's associated file in Azure Blob Storage. |
| `language` | VARCHAR(50) | Nullable | Language used in the conversation. |
| `duration` | INTEGER | Nullable | Duration of the conversation (in seconds). |
```python
class KalimeraConversation(db.Model):
__tablename__ = "kalimera_conversations"
__table_args__ = (
db.PrimaryKeyConstraint("id", name="kalimera_conversation_pkey"),
db.Index("kalimera_conversation_idx", "agent_id", "from_source", "end_user_id", "title"),
)
id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()"))
agent_id = db.Column(StringUUID, nullable=False)
tenant_id = db.Column(StringUUID, nullable=False)
title = db.Column(db.String(255), nullable=False)
status = db.Column(db.String(255), nullable=False) # Active, Completed, Failed, etc.
profile_type = db.Column(db.Enum("inbound", "outbound", name="profile_type_enum"), nullable=False)
instance_id = db.Column(StringUUID, nullable=True) # User who initiated
from_number = db.Column(db.String(255), nullable=False)
to_number = db.Column(db.String(255), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)"))
# Azure Blob Storage URLs
input_file_url = db.Column(db.Text, nullable=True)
output_file_url = db.Column(db.Text, nullable=True)
# Additional metadata
language = db.Column(db.String(50), nullable=True)
duration = db.Column(db.Integer, nullable=True)
```
### Note:
> - ~~Files are stored in Azure-blob and it's URl will be in every message which is stored in invidual conversation.~~
> - ~~All conversations are stored in azure-blob and URL of that will be stored in `KalimeraConversation.blob_url`.~~
> - Instead of the above machenism, we will have `input_file_url` and `output_file_url`
```python
ALLOWED_EXTENSIONS = {'mp3', 'wav', 'ogg', 'flac'}
class KalimeraVoice(Resource):
def get(self, app_model, end_user):
params = request.args.get_json()
if "conversation_id" in params:
conversation_id = request.args.get_json()["conversation_id"]
return file_helpers.get_signed_file_url(app_model + conversation)
# List all blobs under the tenant_id/end_user_id/ path
prefix = f"searchguru/{agent_id}/{end_user_id}/"
print("Prefix:", prefix)
blobs = widget_storage.get_list(starts_with=prefix)
# Download and parse the JSON content
conversations = []
for blob in blobs:
content = widget_storage.load(blob.name)
conversations.append(json.loads(content))
print(len(conversations))
return conversations
def post(self, app_model, end_user):
if 'voice_file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['voice_file']
json_body = request.get_json()
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
if file and allowed_file(file.filename):
# Retrieve additional fields
conversation_id = request.form.get('conversation_id', 'Unknown')
language = request.form.get('language', 'en')
# Store/Append metadata JSON in asynchonous call
cache_key = f"Kalimera_voice_{agentid}_{conversation_id}"
store_data = {
"input_file_url": file_url,
"output_file_url": output_file_url,
"conversation_id": conversation_id,
"language": language,
"profile_type": "inbound/outbound"
"created_at": current_timestamp - TimeDelta(len(file))
"updated_at": current_timestamp
}
stored_data = store_voice_conversation(cache_key, json_body)
store_voice_conversation.apply_async([agentid, end_user.id, conversation_id])
# Return WAV file as response
return {
"status": True,
"message": "Successfully stored."
}
@classmethod
def allowed_file(filename):
"""Check if the uploaded file has a valid extension."""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@classmethod
def upload_to_blob(file_content, filename):
"""Upload content to Azure Blob Storage and return the URL."""
blob_client = container_client.get_blob_client(filename)
blob_client.upload_blob(file_content, overwrite=True)
return blob_client.url
@classmethod
def store_voice_conversation(data, cache_key):
stored_data = redis_client.get(cache_key)
if not stored_data:
# Create conversation
conversation = KalimeraConversation(
name = data["name"] or "",
blob_url = 'kalimera/data["agent_id"]/data["conversation_id"]' + '.json',
created_at = str(datetime.now()),
updated_at = str(datetime.now()),
# Add more which are needed
)
redis_conversation = {
"name":data["name"] or "",
"history": [data]
"created_at":str(datetime.now()),
"updated_at":str(datetime.now()),
}
serialized_results = json.dumps(data_to_store, ensure_ascii=False)
cache_key = f"sg_conversation_{agentid}_{guid}"
redis_client.setex(cache_key, cls.CACHE_TTL, serialized_results)
# If exist then append last message.
stored_data["history"].append(data)
redis_client.set(cache_key, store_data)
api.add_resource(KalimeraVoice, 'kalimera/ai-agent/<uuid:agent_id>/voice')
```
> The above endpoint will take
> Above endpoint will return `.wav` file.
> To Store in azure blob we will use celery task `kalimera_store_conversation`. as below:
```python
def kalimera_store_conversation(agent_id, end_user_id, conversation_id):
blob_path = f'searchguru/{agent_id}/{end_user_id}/{conversation_id}.json'
is_exists = widget_storage.exists(blob_path)
conversation = None
if is_exists:
file_content = widget_storage.load(blob_path)
data = json.loads(file_content)
else:
data = {
"conversation_id": conversation_id,
"title": "New Conversation",
"date_created": str(datetime.now()),
"date_updated": str(datetime.now()),
"messages": []
}
logging.info(f'agent_id, {agent_id}')
if validate_uuid(agent_id):
agent = db.session.query(SearchGuruAgent).filter(SearchGuruAgent.agent_id == agent_id).first()
conversation = SearchGuruConversation(
# Store necessory data
)
db.session.add(conversation)
db.session.commit()
if data:
cache_key = f"Kalimera_voice_{agentid}_{conversation_id}"
new_data = redis_client.get(cache_key)
data.append(new_data)
file_content = json.dumps(data, indent=4)
content_settings = ContentSettings(
content_type="application/json"
)
widget_storage.save(blob_path, file_content, content_settings, overwrite=True)
else:
logging.info("No data to store")
```
---
---
## **Part 3: Conversation Statistics APIs**
### **Overview**
The Conversation Statistics API is designed to provide **Scheduled** insights into conversations between users and AI agents. These statistics are dynamically generated from actual conversations stored in the **Azure-blob**. The endpoints offer data such as sentiment analysis, call breakdowns, and historical trends. (**`This all APIs and data are dynamically generated from the conversation table and data(azure-blob)`**)
### **Key Features**
- **Real-time Statistics**: Tracks active inbound and outbound conversations.
- **Historical Data**: Provides monthly and weekly statistics of interactions (**`If we exclude the current day it will benefits to make it run once a day. Otherwise we need to make it updated by new conversation.`**)
- **Sentiment Analysis**: Categorizes conversations based on user sentiment.
- **Call Breakdown**: Analyzes conversation patterns and provides insights into engagement trends.
- **Recent Logs**: Displays the latest conversation data for quick monitoring.
### **Implementation Approach**
1. **Database Design**: The `Conversation` model stores key details such as agent name, sentiment, start time, and end time.
2. **Query Optimization**: Efficiently fetch large datasets using aggregation functions to compute statistics.
3. **Flask Endpoints**: API routes retrieve real-time and historical statistics for reporting dashboards. But data are calculated by worker asynchronously.
4. **JSON Response Formatting**: Ensures the data is structured for easy visualization and integration.
### **Expected Outcome**
With these APIs, users will be able to monitor AI-driven conversations effectively, gain insights into trends, and optimize interactions based on sentiment analysis and engagement patterns.