Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
80d0aef
Integration Helper Hackathon
Ashutosh619-sudo Sep 9, 2024
88fb968
workspace_id added to conversations
Ashutosh619-sudo Sep 9, 2024
5eaa7dd
Added spotlight app
shreyanshs7 Sep 9, 2024
a58b8d2
added files for spotlight
shreyanshs7 Sep 9, 2024
582d8f7
added queries model
shreyanshs7 Sep 10, 2024
efe0247
added queries model
shreyanshs7 Sep 10, 2024
c18581c
Add spotlight prompt
Sep 10, 2024
12d9538
Added api for recent query and get suggestions
shreyanshs7 Sep 10, 2024
b7c9eb4
Added api for recent query and get suggestions
shreyanshs7 Sep 10, 2024
8a7a375
added api for recent queries and api to call get suggestions
shreyanshs7 Sep 10, 2024
6df4b54
api for help details
shreyanshs7 Sep 10, 2024
4d528cb
fixed prompt and api
Sep 11, 2024
7ff8109
Actions api
Sep 12, 2024
4b88773
feat: Moving to generics and single message API
Shwetabhk Sep 12, 2024
6f85394
feat: Updating URL
Shwetabhk Sep 12, 2024
70b7e49
action api
shreyanshs7 Sep 12, 2024
b0a4c64
prompt changes
Ashutosh619-sudo Sep 12, 2024
0eb99ef
fix: removing unused import
Shwetabhk Sep 12, 2024
ea56804
action api
shreyanshs7 Sep 12, 2024
7cf4f49
action api
shreyanshs7 Sep 12, 2024
fae0816
feat: Integration helper & Spotlight base branch
Shwetabhk Sep 12, 2024
2080d33
fix: fixing app import
Shwetabhk Sep 12, 2024
160568b
added action response
shreyanshs7 Sep 12, 2024
02e3ece
fix: adding comma
Shwetabhk Sep 12, 2024
195cba0
feat: Returning Credit Card Entity Reference
Shwetabhk Sep 12, 2024
b5ddf15
feat: Ensuring prompt is returning right day of week
Shwetabhk Sep 12, 2024
7d27cd5
Field mapping changes
Sep 12, 2024
0e06bbc
reimbursable expense field set
shreyanshs7 Sep 12, 2024
5f33e7a
reimbursable expense field set journal entry
shreyanshs7 Sep 12, 2024
af12a54
added more actions
Ashutosh619-sudo Sep 12, 2024
0689b79
reimbursable expense grouping api
shreyanshs7 Sep 12, 2024
93b3e31
reimbursable expense export state
shreyanshs7 Sep 12, 2024
384c6a0
Merge branch 'spotlight-api' of github.com:fylein/fyle-qbd-api into h…
Shwetabhk Sep 12, 2024
aaf0e82
updated help section prompt
shreyanshs7 Sep 12, 2024
023c335
Add type
Sep 12, 2024
bbc9c7f
Merge branch 'spotlight-api' into hackathon-base
Sep 12, 2024
88b97e4
added suggestion based on page context
Ashutosh619-sudo Sep 12, 2024
2dce048
Enable and disable export settings
Ashutosh619-sudo Sep 12, 2024
5856962
Merge branch 'spotlight-api' into hackathon-base
Ashutosh619-sudo Sep 13, 2024
2537727
Changed wrong message and code
Ashutosh619-sudo Sep 13, 2024
34c915c
fixed prompt actions message
Ashutosh619-sudo Sep 13, 2024
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
Empty file.
6 changes: 6 additions & 0 deletions apps/integration_helper/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

# Register your models here.
from apps.integration_helper.models import Conversation

admin.site.register(Conversation)
5 changes: 5 additions & 0 deletions apps/integration_helper/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class IntegrationHelperConfig(AppConfig):
name = 'apps.integration_helper'
27 changes: 27 additions & 0 deletions apps/integration_helper/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.1.14 on 2024-09-09 14:14

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Conversation',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('conversation_id', models.CharField(help_text='Unique id of the conversation', max_length=255)),
('role', models.CharField(help_text='Role of the messenger', max_length=255)),
('content', models.TextField(help_text='Content of the message')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Created at datetime')),
],
options={
'db_table': 'conversations',
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.1.14 on 2024-09-09 14:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('integration_helper', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='conversation',
name='workspace_id',
field=models.IntegerField(help_text='Workspace id of the organization'),
preserve_default=False,
),
]
Empty file.
15 changes: 15 additions & 0 deletions apps/integration_helper/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.db import models


class Conversation(models.Model):
id = models.AutoField(primary_key=True)
conversation_id = models.CharField(max_length=255, help_text="Unique id of the conversation")
workspace_id = models.IntegerField(help_text="Workspace id of the organization")
role = models.CharField(max_length=255, help_text="Role of the messenger")
content = models.TextField(help_text="Content of the message")
created_at = models.DateTimeField(
auto_now_add=True, help_text="Created at datetime"
)

class Meta:
db_table = 'conversations'
25 changes: 25 additions & 0 deletions apps/integration_helper/openai_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import os
from openai import OpenAI
from apps.integration_helper.prompt import PROMPT
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove unused import.

The apps.integration_helper.prompt.PROMPT import is unused in this file. Please remove it to keep the code clean.

Tools
Ruff

3-3: apps.integration_helper.prompt.PROMPT imported but unused

Remove unused import: apps.integration_helper.prompt.PROMPT

(F401)

import json


# OpenAI API Key Setup

def get_openai_response(messages):
"""
Send the conversation history (messages) to OpenAI and get a response.
"""
api_key = os.getenv("OPENAI_API_KEY")
client = OpenAI(
api_key=api_key
)
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
response_format={"type": "json_object"},
max_tokens=1000,
temperature=0,
)

return json.loads(response.choices[0].message.content)
Comment on lines +9 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling and consider moving hardcoded parameters to configuration.

The function looks good overall, with a few suggestions for improvement:

  1. Good job on using an environment variable for the API key. This keeps sensitive information out of the codebase. Just ensure that the OPENAI_API_KEY environment variable is properly set in all environments where this code will run.

  2. Consider adding error handling for potential issues like missing API key, invalid API key, or network errors. This will prevent uncaught exceptions and make the function more resilient.

  3. The function currently uses hardcoded parameters for the model, response format, max tokens, and temperature. While this may be intentional, consider moving these to configuration variables to make it easier to change them in the future if needed.

128 changes: 128 additions & 0 deletions apps/integration_helper/prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
PROMPT = """
You are an Expense Management software assistant designed to help users conversationally set up their QuickBooks Desktop Integration. Your goal is to ask the user questions about their preferences, gather their responses, and ultimately return a final JSON payload that reflects their settings.

=========================================================================================================================
STEP 1: Export Settings

Your first task is to guide the user through the export settings for both Reimbursable Expenses and Credit Card Expenses. You must first determine the Export Type for each before proceeding with the sub-questions. The user can choose one or both categories, and for any category they don’t select, return `null` for the fields related to that category.

### For Reimbursable Expenses (They can choose either Bills or Journal Entries as Export Type):
- If they choose **Bills**, ask:
- What is the name of the bank account you want to use for Accounts Payable?
- What is the name of the Mileage Account (if applicable)? (This is optional)
- The rest of the settings will be hardcoded and skipped:
- Expenses will be grouped by "REPORT"
- The Date of Export will be based on the last expense spent
- The state will be "PAYMENT_PROCESSING"

- If they choose **Journal Entries**, ask:
- What is the name of the bank account you want to use for Accounts Payable?
- What is the name of the Mileage Account (if applicable)? (This is optional)
- The same hardcoded settings will apply as above.

### For Card Expenses (They can choose either Credit Card Purchases or Journal Entries as Export Type):
- If they choose **Credit Card Purchases**, ask:
- What is the name of the Credit Card Account you want to use?
- The rest of the settings will be hardcoded and skipped:
- Expenses will always be grouped by "EXPENSE"
- Purchased From (credit_card_entity_name_preference) will always be "VENDOR"
- The Date of Export will always be the spend date, no user input required
- The state always will be "APPROVED", no user input required

- If they choose **Journal Entries**, ask:
- What is the name of the Credit Card Account you want to use?
- The same hardcoded settings will apply as above.
- Purchased From (credit_card_entity_name_preference) will always be "VENDOR"

=========================================================================================================================
STEP 2: Field Mapping

Next, you'll ask the user if they want to map Projects and Classes to their expenses.

- If they choose to map **Projects**, you will hardcode it to "Project".
- If they choose to map **Classes**, you will hardcode it to "Cost Center".
- The **Item** field will not be asked and will always be returned as `null`.

=========================================================================================================================
STEP 3: Advanced Settings

Lastly, you'll guide the user through the advanced settings where they can choose to schedule the export. Ask if they want to enable the scheduling feature, and if so, prompt them to set the frequency. The options are Daily, Weekly, or Monthly:

- **Daily**: Ask for the time of day.
- **Weekly**: Ask for the day of the week and time of day.
- **Monthly**: Ask for the day of the month and time of day.

Other advanced settings will be hardcoded and should not be asked:
- Emails will default to an empty list.
- Top Memo Structure will be set to include "employee_email".
- Expense Memo Structure will be set to include "employee_email", "merchant", "purpose", "category", "spent_on", "report_number", and "expense_link".

=========================================================================================================================
FINAL OUTPUT:

Your responses can only be in the form of below JSONs:

For CONVERSATION:
{
"output_type": "CONVERSATION", // FINAL for the FINAL JSON PAYLOAD and CONVERSATION for questions
"output": {
"question": "What is the name of the bank account you want to use for Accounts Payable?", // this question is just an example
}
}

For FINAL:
{
"output_type": "FINAL", // FINAL for the FINAL JSON PAYLOAD and CONVERSATION for questions
"output_export_settings": {
"reimbursable_expenses_export_type": "BILL",
"bank_account_name": "Accounts Payable",
"mileage_account_name": "Mileage",
"reimbursable_expense_state": "PAYMENT_PROCESSING",
"reimbursable_expense_date": "last_spent_at",
"reimbursable_expense_grouped_by": "REPORT",
"credit_card_expense_export_type": "CREDIT_CARD_PURCHASE",
"credit_card_expense_state": "APPROVED",
"credit_card_entity_name_preference": "VENDOR",
"credit_card_account_name": "Capital One 2222",
"credit_card_expense_grouped_by": "EXPENSE",
"credit_card_expense_date": "spent_at"
},
"output_field_mapping": {
"class_type": "COST_CENTER",
"project_type": "PROJECT",
"item_type": null
},
"output_advanced_settings": {
"expense_memo_structure": [
"employee_email",
"merchant",
"purpose",
"category",
"spent_on",
"report_number",
"expense_link"
],
"top_memo_structure": [
"employee_email"
],
"schedule_is_enabled": true,
"emails_selected": [],
"day_of_month": null,
"day_of_week": null, // If not null should always be CAPITALIZED (MONDAY, TUESDAY, etc.)
"frequency": "DAILY",
"time_of_day": "12:00:00"
}
}

=========================================================================================================================

Ensure the following guidelines:

1. **Ask Questions Step-by-Step:** Return one question at a time in JSON format unless the user provides all information at once.
2. **Confusing Answers Clarification:** If the user answer is confusing and gibberish, please ask clarification questions.
3. **If User Provides Info:** Respond with the next appropriate question.
4. **Final Output:** Once all questions are answered and all steps are answered, output the final JSON payload as specified.
5. **Return JSON:** Return all the keys even if the value is `null`.
6. **Steps Ensurity:** Ensure every step has been answered, never give final JSON without completing all steps.

"""
3 changes: 3 additions & 0 deletions apps/integration_helper/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the unused import statement.

The TestCase import from django.test is unused in this file.

Apply this diff to remove the unused import:

-from django.test import TestCase
-
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from django.test import TestCase
Tools
Ruff

1-1: django.test.TestCase imported but unused

Remove unused import: django.test.TestCase

(F401)


# Create your tests here.
Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add test cases for the integration_helper app.

The test file is currently empty and does not contain any test cases. It's important to have a comprehensive test suite to ensure the correctness and reliability of the integration_helper app.

Do you want me to generate some test cases for the integration_helper app or open a GitHub issue to track this task?

Tools
Ruff

1-1: django.test.TestCase imported but unused

Remove unused import: django.test.TestCase

(F401)

7 changes: 7 additions & 0 deletions apps/integration_helper/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.urls import path
from .views import CoversationsView


urlpatterns = [
path(route='', view=CoversationsView.as_view(), name='conversations')
]
92 changes: 92 additions & 0 deletions apps/integration_helper/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import uuid
from rest_framework import status, generics
from rest_framework.response import Response
from apps.integration_helper.models import Conversation
from apps.integration_helper.openai_utils import get_openai_response
from apps.integration_helper.prompt import PROMPT


class CoversationsView(generics.CreateAPIView, generics.DestroyAPIView):
"""
View for creating and deleting conversations.
"""

def create(self, request, *args, **kwargs):
"""
Create a new conversation and get the first OpenAI response.
"""
content = request.data.get('content')
conversation_id = request.data.get('conversation_id')
workspace_id = kwargs['workspace_id']

if not content:
return Response(
{'error': 'content are required'}, status=status.HTTP_400_BAD_REQUEST
)

if not conversation_id:
conversation_id = str(uuid.uuid4())

Conversation.objects.update_or_create(
defaults={'content': PROMPT},
conversation_id=conversation_id,
workspace_id=workspace_id,
role='system'
)

conversation = Conversation.objects.create(
conversation_id=conversation_id,
workspace_id=workspace_id,
role='user',
content=content
)

messages = list(
Conversation.objects.filter(
conversation_id=conversation_id,
workspace_id=workspace_id
).values('role', 'content').order_by('created_at'))

assistant_response = get_openai_response(messages)

Conversation.objects.create(
conversation_id=conversation_id,
workspace_id=workspace_id,
role='assistant',
content=assistant_response,
)

return Response(
{
'conversation_id': conversation.conversation_id,
'content': assistant_response,
},
status=status.HTTP_201_CREATED,
)
Comment on lines +14 to +65
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Consider extracting the OpenAI API interaction logic and improving error handling.

The create method follows a clear sequence of steps to create a new conversation and retrieve the assistant response from the OpenAI API. The implementation looks good overall.

Suggestions for further improvement:

  1. Consider extracting the OpenAI API interaction logic (lines 44-50) into a separate method for better code organization and reusability.
  2. Add more detailed error handling and logging throughout the method to facilitate better troubleshooting and monitoring.


def delete(self, request, *args, **kwargs):
"""
Clear the conversation history by deleting it using conversation_id.
"""
workspace_id = kwargs['workspace_id']
conversation_id = request.data.get('conversation_id')
if not conversation_id:
return Response(
{
'error': 'conversation_id is required'
}, status=status.HTTP_400_BAD_REQUEST
)

conversations = Conversation.objects.filter(
conversation_id=conversation_id,
workspace_id=workspace_id
)

if conversations.exists():
conversations.delete()

return Response(
{
'message': 'Conversation cleared'
}, status=status.HTTP_200_OK
)
Comment on lines +67 to +92
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Consider adding a confirmation step and a more informative response.

The delete method correctly handles the deletion of conversations based on the provided conversation_id and workspace_id. The implementation looks good overall.

Suggestions for further improvement:

  1. Consider adding a confirmation step before deleting the conversations to prevent accidental deletions. This could be achieved by requiring an additional parameter in the request data to confirm the deletion.
  2. Instead of returning a generic success message, consider returning a more informative response indicating the number of conversations deleted. This would provide better feedback to the client about the outcome of the operation.

Empty file added apps/spotlight/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions apps/spotlight/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the unused import.

The django.contrib.admin import is unused in this file. It's a good practice to remove unused imports to keep the code clean and maintainable.

Apply this diff to remove the unused import:

-from django.contrib import admin
-
 
 # Register your models here.
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from django.contrib import admin
# Register your models here.
Tools
Ruff

1-1: django.contrib.admin imported but unused

Remove unused import: django.contrib.admin

(F401)


# Register your models here.
5 changes: 5 additions & 0 deletions apps/spotlight/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class SpotlightConfig(AppConfig):
name = 'spotlight'
Loading
Loading