-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Integration helper & Spotlight base branch #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
80d0aef
88fb968
5eaa7dd
a58b8d2
582d8f7
efe0247
c18581c
12d9538
b7c9eb4
8a7a375
6df4b54
4d528cb
7ff8109
4b88773
6f85394
70b7e49
b0a4c64
0eb99ef
ea56804
7cf4f49
fae0816
2080d33
160568b
02e3ece
195cba0
b5ddf15
7d27cd5
0e06bbc
5f33e7a
af12a54
0689b79
93b3e31
384c6a0
aaf0e82
023c335
bbc9c7f
88b97e4
2dce048
5856962
2537727
34c915c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) |
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' |
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, | ||
), | ||
] |
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' |
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 | ||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
|
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. | ||
|
||
""" |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,3 @@ | ||||
from django.test import TestCase | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the unused import statement. The Apply this diff to remove the unused import: -from django.test import TestCase
- Committable suggestion
Suggested change
ToolsRuff
|
||||
|
||||
# Create your tests here. | ||||
Comment on lines
+1
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add test cases for the 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 Do you want me to generate some test cases for the ToolsRuff
|
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') | ||
] |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Suggestions for further improvement:
|
||
|
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM! Consider adding a confirmation step and a more informative response. The Suggestions for further improvement:
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,3 @@ | ||||||
from django.contrib import admin | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the unused import. The Apply this diff to remove the unused import: -from django.contrib import admin
-
# Register your models here. Committable suggestion
Suggested change
ToolsRuff
|
||||||
|
||||||
# Register your models here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class SpotlightConfig(AppConfig): | ||
name = 'spotlight' |
There was a problem hiding this comment.
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