Next generation CRM
import os
import json
import datetime
import time
import pandas
import requests
import openai
# Complete the values
my_config = {
"OPENAI_API_KEY": "...",
}
class CustomerConversations:
def __init__(self):
self.conversations_df = pandas.read_csv("conversations.csv")
def get_conversations_for_chat_id(self, chat_id: str):
filtered_df = self.conversations_df[self.conversations_df["chat_id"] == chat_id]
filtered_df = filtered_df.sort_values(by="timestamp_utc", ascending=True)
res = "Conversation history for chat_id " + chat_id + ":\n"
for index, row in filtered_df.iterrows():
res += (
"Message from "
+ row["user_firstname"]
+ " "
+ row["user_lastname"]
+ " (chat_username: "
+ row["user_username"]
+ " ) at "
+ row["timestamp_utc"]
+ ":\n"
)
res += row["message"] + "\n\n"
return res
customer_conversations_instance = CustomerConversations()
chat_id = "C-123456789"
conversation_for_chat_id = customer_conversations_instance.get_conversations_for_chat_id(chat_id)
print(conversation_for_chat_id[0:2000] + "\n...")
import importlib
import crm_service as crm_service
importlib.reload(crm_service)
print("CRM service loaded.")
class AIAgent:
def __init__(self, chat_id: str, my_config: dict):
self.chat_id = chat_id
self.my_config = my_config
self.crm_service = crm_service.CRMService(chat_id, my_config)
self.openai_api_key = my_config.get("OPENAI_API_KEY", "")
self.llm_client = openai.OpenAI(api_key=self.openai_api_key)
self.system_prompt = """You are a CRM Assistant for the company AIEXPERT. Your primary role is to:
1. Record information from conversations into the CRM's database tables:
- Contacts: Store personal and professional details about external individuals (each contact has a unique contact_id). Internal salespeople are not contacts.
- Companies: Maintain information about organizations (each company has a unique company_id)
- Interactions: Track all communications and interactions with external individuals (each interaction has a unique interaction_id). An interaction is the summary of a conversation, not a single message. The summary should put particular emphasis on decisions agreed and next steps.
- Deals: Manage business opportunities and sales pipeline (each deal has a unique deal_id)
2. Retrieve information from these tables in response to user queries
3. Ignore requests that are unrelated to CRM functions
Your focus is on maintaining accurate and up-to-date CRM data. When processing conversations, prioritize extracting relevant business information and relationships. When responding to queries, provide clear, data-driven answers based on the CRM database.
"""
self.tools = [
{
"type": "function",
"name": "create_contact",
"description": "Create a new contact in the CRM database. Returns the full contact object including its unique contact_id. Each contact is associated with a unique chat_username.",
"parameters": {
"type": "object",
"properties": {
"full_name": {
"type": "string",
"description": "The full name of the contact.",
},
"company_id": {
"type": "string",
"description": "The id of the company the contact belongs to.",
},
"chat_username": {
"type": "string",
"description": "The username of the chat.",
},
"job_title": {
"type": ["string", "null"],
"description": "The job title of the contact.",
},
"email": {
"type": ["string", "null"],
"description": "The email address of the contact.",
},
"phone": {
"type": ["string", "null"],
"description": "The phone number of the contact.",
},
"address_street": {
"type": ["string", "null"],
"description": "The street address of the contact.",
},
"address_city": {
"type": ["string", "null"],
"description": "The city of the contact.",
},
"address_state": {
"type": ["string", "null"],
"description": "The state of the contact.",
},
"address_zip": {
"type": ["string", "null"],
"description": "The zip code of the contact.",
},
"address_country": {
"type": ["string", "null"],
"description": "The country of the contact.",
},
},
"required": [
"full_name",
"company_id",
"chat_username",
"job_title",
"email",
"phone",
"address_street",
"address_city",
"address_state",
"address_zip",
"address_country",
],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_industry_sectors",
"description": "Get the list of industry sectors authorized for the industry_sector field of the companies table.",
"parameters": {
"type": "object",
"properties": {},
"required": [],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "create_company",
"description": "Create a new company in the CRM database. Returns the full company object including its unique company_id. Each company is associated with a unique chat_id.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the company.",
},
"chat_id": {
"type": "string",
"description": "The chat_ id the chat.",
},
"website": {
"type": ["string", "null"],
"description": "The website of the company.",
},
"industry_sector": {
"type": ["string", "null"],
"description": "The industry sector of the company. Must be one of the authorized industry sectors from the get_industry_sectors function.",
},
},
"required": ["name", "chat_id", "website", "industry_sector"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_interaction_types",
"description": "Get the list of interaction types authorized for the interaction_type field of the interactions table.",
"parameters": {
"type": "object",
"properties": {},
"required": [],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "create_interaction",
"description": (
"Create a new interaction in the CRM database. Returns the full interaction object including its unique interaction_id. "
+ "An interaction is the summary of a conversation, not a single message. The summary should put particular emphasis on decisions agreed and next steps."
),
"parameters": {
"type": "object",
"properties": {
"contact_id": {
"type": "string",
"description": "The contact_id of the external customer contact, from the contacts table. This should not be the internal salesperson.",
},
"company_id": {
"type": "string",
"description": "The company_id of the external customer company, from the companies table.",
},
"interaction_type": {
"type": "string",
"description": (
"The type of interaction. Must be one of the authorized interaction types from the get_interaction_types function "
+ "such as 'Email', 'Phone', 'Meeting', 'Telegram', 'WhatsApp', 'Other'."
),
},
"interaction_notes": {
"type": "string",
"description": "The summary of the interaction.",
},
},
"required": [
"contact_id",
"company_id",
"interaction_type",
"interaction_notes",
],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_deal_stages",
"description": "Get the list of deal stages authorized for the deal_stage field of the deals table.",
"parameters": {
"type": "object",
"properties": {},
"required": [],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "create_deal",
"description": (
"Create a new deal in the CRM database. Returns the full deal object including its unique deal_id. "
+ "Before creating a deal, query the list of deals associated with the company_id, to ensure that the deal does not already exist."
),
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the deal.",
},
"company_id": {
"type": "string",
"description": "The company_id of the company, from the companies table. This is not the chat_id.",
},
"deal_stage": {
"type": "string",
"description": (
"The stage of the deal. Must be one of the authorized deal stages from the get_deal_stages function. "
+ "Default is prospect. Other stages are 'lead', 'opportunity', 'closed-won', 'closed-lost'."
),
},
"deal_amount": {
"type": "number",
"description": "The maximum amount of the deal. Default is 0.0.",
},
"deal_currency": {
"type": "string",
"description": "The currency of the deal using 3-letter ISO code. Default is USD.",
},
},
"required": [
"name",
"company_id",
"deal_stage",
"deal_amount",
"deal_currency",
],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_contact_by_id",
"description": "Get a contact by contact_id. Return the contact object including its unique contact_id.",
"parameters": {
"type": "object",
"properties": {
"contact_id": {
"type": "string",
"description": "The id of the contact, from the contacts table.",
}
},
"required": ["contact_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_company_by_id",
"description": "Get a company by company_id. Return the company object including its unique company_id.",
"parameters": {
"type": "object",
"properties": {
"company_id": {
"type": "string",
"description": "The id of the company, from the companies table.",
}
},
"required": ["company_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_interaction_by_id",
"description": "Get an interaction by interaction_id. Return the interaction object including its unique interaction_id.",
"parameters": {
"type": "object",
"properties": {
"interaction_id": {
"type": "string",
"description": "The id of the interaction, from the interactions table.",
}
},
"required": ["interaction_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_deal_by_id",
"description": "Get a deal by deal_id. Return the deal object including its unique deal_id.",
"parameters": {
"type": "object",
"properties": {
"deal_id": {
"type": "string",
"description": "The id of the deal, from the deals table.",
}
},
"required": ["deal_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "query_contacts",
"description": "Query the contacts table based on partial name and company_id. Return the list of contacts objects, each including its unique contact_id.",
"parameters": {
"type": "object",
"properties": {
"partial_name": {
"type": "string",
"description": "The partial name of the contact.",
},
"company_id": {
"type": "string",
"description": "The id of the company, from the companies table.",
},
},
"required": ["partial_name", "company_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_contact_by_chat_username",
"description": "Get a contact by chat_username. Return the contact object including its unique contact_id.",
"parameters": {
"type": "object",
"properties": {
"chat_username": {
"type": "string",
"description": "The username of the chat.",
},
},
"required": ["chat_username"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_company_by_chat_id",
"description": "Finds the company object associated with the chat_id. Return the company object including its unique company_id.",
"parameters": {
"type": "object",
"properties": {
"chat_id": {
"type": "string",
"description": "The chat_id of the chat.",
},
},
"required": ["chat_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_interactions_by_company_id",
"description": "Query the interactions table by company_id in chronological order. Return the list of interactions objects.",
"parameters": {
"type": "object",
"properties": {
"company_id": {
"type": "string",
"description": "The company_id of the company, from the companies table. This is not the chat_id.",
},
},
"required": ["company_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "get_deals_by_company_id",
"description": "Query the deals table by company_id. Return the list of deals objects.",
"parameters": {
"type": "object",
"properties": {
"company_id": {
"type": "string",
"description": "The company_id of the company, from the companies table. This is not the chat_id.",
},
},
"required": ["company_id"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "update_contact",
"description": "Update a contact in the CRM database. Returns the updated contact object.",
"parameters": {
"type": "object",
"properties": {
"contact_id": {
"type": "string",
"description": "The contact_id of the contact, from the contacts table.",
},
"updated_fields": {
"type": "string",
"description": (
"The fields to update, as a stringified JSON object. Must be a dictionary with the field names as keys and the new values as values. "
+ "Allowed fields are: full_name, company_id, job_title, email, phone, address_street, address_city, "
+ "address_state, address_zip, address_country."
),
},
},
"required": ["contact_id", "updated_fields"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "update_company",
"description": "Update a company in the CRM database. Returns the updated company object.",
"parameters": {
"type": "object",
"properties": {
"company_id": {
"type": "string",
"description": "The company_id of the company, from the companies table.",
},
"updated_fields": {
"type": "string",
"description": (
"The fields to update, as a stringified JSON object. Must be a dictionary with the field names as keys and the new values as values. "
+ "Allowed fields are: name, website, industry_sector."
),
},
},
"required": ["company_id", "updated_fields"],
"additionalProperties": False,
},
"strict": True,
},
{
"type": "function",
"name": "update_deal",
"description": "Update a deal in the CRM database. Returns the updated deal object.",
"parameters": {
"type": "object",
"properties": {
"deal_id": {
"type": "string",
"description": "The deal_id of the deal, from the deals table.",
},
"updated_fields": {
"type": "string",
"description": (
"The fields to update, as a stringified JSON object. Must be a dictionary with the field names as keys and the new values as values. "
+ "Allowed fields are: name, deal_stage, deal_amount, deal_currency."
),
},
},
"required": ["deal_id", "updated_fields"],
"additionalProperties": False,
},
"strict": True,
},
]
def handle_user_conversation(self, conversation: str):
"""
Handle a user conversation.
The format of a user conversation is:
[
{
"role": "developer",
"content": "system message"
},
{
"role": "user",
"content": "message"
},
{
"role": "assistant",
"content": "message"
}
]
"""
keep_going = True
n_iterations = 0
final_response = None
while keep_going and n_iterations < 20:
n_iterations += 1
llm_response = self.llm_client.responses.create(
model="gpt-4.1",
input=conversation,
tools=self.tools,
)
llm_response_output = llm_response.output
print("\nLLM response output:", llm_response_output)
# Process tool calls
has_tool_call = False
for tool_call in llm_response_output:
print("Tool call:", tool_call)
if tool_call.type == "function_call":
has_tool_call = True
conversation.append(tool_call)
tool_call_response = self.crm_service.call_function(
tool_call.name, tool_call.arguments
) # This is a string
print("Tool call response:", tool_call_response)
tool_call_result = {
"type": "function_call_output",
"call_id": tool_call.call_id,
"output": tool_call_response,
}
conversation.append(tool_call_result)
if not has_tool_call:
final_response = llm_response.output_text
print("No tool call, final response:", final_response)
final_response_obj = {"role": "assistant", "content": final_response}
conversation.append(final_response_obj)
keep_going = False
if final_response is None:
final_response = "The assistant did not manage to complete the task."
final_response_obj = {"role": "assistant", "content": final_response}
conversation.append(final_response_obj)
return final_response, conversation
chat_id = "C-123456789"
ai_agent_instance = AIAgent(chat_id, my_config)
customer_conversation = customer_conversations_instance.get_conversations_for_chat_id(
chat_id
)
prompt = (
"""A customer conversation, captured from a Telegram chat, is provided below between the <customer_conversation> tags.
Use the conversation to create or update the CRM database, including the contacts, companies, interactions and deals tables.
Remember that an interaction is the summary of a conversation, not a single message.
When you are done, respond with a summary of the actions you took, and the data you created or updated in the CRM database.
<customer_conversation>
"""
+ customer_conversation
+ """
</customer_conversation>
"""
)
llm_conversation = [
{
"role": "developer",
"content": ai_agent_instance.system_prompt,
},
{
"role": "user",
"content": prompt,
},
]
final_response, llm_conversation = ai_agent_instance.handle_user_conversation(
llm_conversation
)
print("\nFINAL RESPONSE:\n")
print(final_response)
chat_id = "C-123456789"
ai_agent_instance = AIAgent(chat_id, my_config)
prompt = f"For the company associated with chat_id {chat_id}, who are the main contacts and what are the deals under way?."
llm_conversation = [
{
"role": "developer",
"content": ai_agent_instance.system_prompt,
},
{
"role": "user",
"content": prompt,
},
]
final_response, llm_conversation = ai_agent_instance.handle_user_conversation(
llm_conversation
)
print("\nFINAL RESPONSE:\n")
print(final_response)