Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1f9b6d2
NextAI Parsing
manikandanmohan10 Nov 8, 2025
4479ab8
Merge pull request #77 from erpnextai/m-phase3
manikandanmohan10 Nov 8, 2025
d4b755c
Parsing Demo API
samyuktha061098 Nov 11, 2025
995a1a1
Merge pull request #78 from erpnextai/a-parsing
erpnextai Nov 11, 2025
6eee41c
response for todo
samyuktha061098 Nov 11, 2025
c614711
Merge pull request #79 from erpnextai/a-parsing
erpnextai Nov 11, 2025
fc3ab1b
BUG: JSON Field Added
samyuktha061098 Nov 20, 2025
6762f5b
Parsing fields & Dynamic Parsing Method
samyuktha061098 Nov 20, 2025
00aa413
Merge pull request #80 from erpnextai/a-parsing
erpnextai Nov 20, 2025
21a20ca
parser update
samyuktha061098 Nov 24, 2025
c7f4530
Merge pull request #81 from erpnextai/a-parsing
erpnextai Nov 24, 2025
602b9df
Parsing Backend MVP
samyuktha061098 Nov 30, 2025
c3f8f08
Parsing Backend MVP
erpnextai Nov 30, 2025
b30692b
Parsing Backend MVP
samyuktha061098 Nov 30, 2025
412c2e9
NextAI Parsing UI changes
manikandanmohan10 Dec 1, 2025
46d231c
NextAI Parsing
manikandanmohan10 Dec 3, 2025
257edb0
Code optimization
manikandanmohan10 Dec 3, 2025
357644e
Code optimizing
manikandanmohan10 Dec 3, 2025
7ac8aec
Merge pull request #83 from erpnextai/mani-dev
manikandanmohan10 Dec 3, 2025
908adb3
NextAI Parsing
manikandanmohan10 Dec 13, 2025
7a7ac2c
Adding window.cur_frm
manikandanmohan10 Dec 13, 2025
2d48dfd
Merge pull request #84 from erpnextai/mani-dev
manikandanmohan10 Dec 13, 2025
e97a6d0
NextAI Parsing
manikandanmohan10 Dec 14, 2025
4ad4f42
Merge pull request #85 from erpnextai/mani-dev
manikandanmohan10 Dec 14, 2025
4de1aad
Update ci.yml
manikandanmohan10 Jan 12, 2026
2d39fba
Update ci.yml
manikandanmohan10 Jan 12, 2026
cf2a86b
Update ci.yml
manikandanmohan10 Jan 12, 2026
2a1c5e6
Update ci.yml
manikandanmohan10 Jan 12, 2026
6e65400
Update ci.yml
manikandanmohan10 Jan 12, 2026
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
24 changes: 20 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,28 @@ jobs:
sudo apt update
sudo apt-get install -y mariadb-client

# - name: Setup Bench & DB
# run: |
# pip install frappe-bench<=15
# bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
# mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
# mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"

- name: Setup Bench & DB
run: |
pip install frappe-bench
bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
pip install "frappe-bench<6"
bench init ~/frappe-bench \
--frappe-branch version-15 \
--skip-redis-config-generation \
--skip-assets \
--python "$(which python)"

mariadb --host 127.0.0.1 --port 3306 -u root -proot \
-e "SET GLOBAL character_set_server = 'utf8mb4'"

mariadb --host 127.0.0.1 --port 3306 -u root -proot \
-e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"


- name: Install App
working-directory: /home/runner/frappe-bench
Expand Down
158 changes: 158 additions & 0 deletions next_ai/ai/parsing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import frappe
import os
from langchain_google_genai import ChatGoogleGenerativeAI
from frappe import _
from pydantic import BaseModel, Field, create_model
from next_ai.ai.utils import get_parser_type_details
from next_ai.ai.prompt import PARSING_PROMPT


@frappe.whitelist(methods=["POST"])
def get_ai_parser_response(**kwargs):
"""
doctype: str - The name of the doctype to parse data for.
name: str - The name of the document instance.
message: str - The user input message to be parsed.
"""
data = frappe._dict(kwargs)

# Mandatory fields
required_fields = ["doctype", "name", "message"]

missing = [f for f in required_fields if not data.get(f)]
if missing:
frappe.throw(f"Missing mandatory fields: {', '.join(missing)}")

message = NextAIParsing(api_data=data).get_response()
return {"status_code":200, "status": "sucess", "message": message}


@frappe.whitelist(methods=["GET"])
def get_parser_fieldtype_details():
parser_type_details = get_parser_type_details()
return {"status_code":200, "status": "sucess", "message": parser_type_details}


class NextAIParsing:
"""
This class is mainly used to get the user input and parse the data inside the doctype fields.
"""
def __init__(self, api_data: dict):
self.api_data = api_data

self.message = self.api_data.get("message")

self.parsing_fields = self.get_parsing_field_details()
self.field_meta = get_parser_type_details()

self.nextai_settings = self.get_nextai_settings()
self.validate_settings()

self.current_model = self.nextai_settings.model_name

def validate_token(self):
if len(self.api_data['message']) > 8000:
frappe.throw(_("Prompt length exceeds the maximum limit of 8000 characters. Please shorten your prompt."))

def validate_settings(self):
if not self.nextai_settings.model_name:
frappe.throw(_("Model name is not set in NextAI Settings. Please configure the model name."))
if not self.nextai_settings.platform:
frappe.throw(_("Platform is not set in NextAI Settings. Please configure the platform."))
if not self.nextai_settings.get_password("api_key"):
frappe.throw(_("API Key is not set in NextAI Settings. Please configure the API Key."))

def get_nextai_settings(self):
nextai_settings = frappe.get_doc('NextAI Settings')
return nextai_settings

def get_llm(self, model_name: str = None):
model_name = model_name or self.nextai_settings.model_name
try:
if self.nextai_settings.platform == 'Gemini':
os.environ['GOOGLE_API_KEY'] = self.nextai_settings.get_password("api_key")
llm = ChatGoogleGenerativeAI(model=model_name)
return llm
except Exception as e:
frappe.log_error(frappe.get_traceback(), "Error in NextAIParsing.get_llm")
frappe.throw(_("Error in getting LLM: {0}").format(str(e)))

def get_structured_output_llm(self, model_name: str = None, base_model: BaseModel = None):
llm = self.get_llm(model_name=model_name)
so_llm = llm.with_structured_output(base_model)
return so_llm

def get_prompt_message(self) -> str:
prompt = PARSING_PROMPT.format(input=self.message)
return prompt

def get_response(self) -> dict:
DynamicBaseParser = self.build_dynamic_parser()
so_llm = self.get_structured_output_llm(model_name=self.current_model, base_model=DynamicBaseParser)
message = self.get_prompt_message()
response = so_llm.invoke(message)
response = response.__dict__
return response

def build_dynamic_parser(self):
model_fields = {}

for field_details in self.parsing_fields:
fields_type_detail = self.field_meta.get(field_details['field_type'])
if not fields_type_detail['is_active']:
continue

model_fields[field_details['field_name']] = {}
_type, default_description = fields_type_detail['type'], fields_type_detail['default_description']

model_fields[field_details['field_name']]['type'] = _type
model_fields[field_details['field_name']]['description'] = field_details.get('description') or default_description

default_value = None

model_fields[field_details['field_name']] = (
_type,
Field(default_value, description=model_fields[field_details['field_name']]['description'])
)
# dynamically create a pydantic model
DynamicBaseParser = create_model("DynamicBaseParser", **model_fields)
return DynamicBaseParser

def test_dynamic_parser(self):
fields = {
"status": {"type": str, "description": "Status of the task"},
"description": {"type": str, "description": "Description of the task"},
"priority": {"type": str, "description": "Priority level"},
"is_completed": {"type": bool, "description": "Completion status"}
}

DynamicParser = self.build_dynamic_parser(fields)

# Example usage
example_data = {
"status": "Open",
"description": "This is a test task.",
"priority": "High",
"is_completed": False
}

parsed_instance = DynamicParser(**example_data)
frappe.logger().info(f"Parsed Instance: {parsed_instance}")
return parsed_instance


def get_parsing_field_details(self) -> dict:
"""
Get the field details for a given parser field name.
"""
data = frappe.db.get_all(
"NextAI Parsing Details",
filters={
"parenttype": "NextAI Parsing",
"parentfield": "parsing_details",
"parent": self.api_data.get("name"),
},
fields=['*'],
order_by='idx'
)
return data
31 changes: 30 additions & 1 deletion next_ai/ai/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@
{input}
"""

JSON = """
You are a software enginer. You generate data in the JSON Format Only. Follow the instructions below:

# Instructions
1. Read the User Input carefully.
2. Generate a response that is relevant to the User Input.
3. Ensure that the response is formatted in the JSON Only Strictly.
4. Do not include any additional text or explanations outside of the code block.
5. Follow the best Indentation and coding practices.

# User Input
{input}
"""

PROMPTS = {
"Markdown Editor": MARKDOWN,
Expand All @@ -102,4 +115,20 @@
"HTML Editor": HTML_EDITOR,
"Text Editor": TEXT_EDITOR,
"Code": CODE,
}
"JSON": JSON
}


PARSING_PROMPT = """
You are an expert data parser. Your task is to extract and structure information based on the provided field definitions. Follow the instructions below:

# Instructions
1. Read the Field Definitions carefully.
2. Read the User Input carefully.
3. Extract relevant information from the User Input based on the Field Definitions.
4. Ensure the extracted information more grammatically correct and contextually relevant.
5. If any date related incomplete information contains, use current time infomration to fill the missing details.

# User Input
{input}
"""
Loading