- Move global config files to globals iso global folder, as the name global conflicts with python language
- Creation of Traicie Vancancy Definition specialist - Allow to invoke non-interaction specialists from withing Evie's mgmt interface (eveai_app) - Improvements to crewai specialized classes - Introduction to json editor for showing specialists arguments and results in a better way - Introduction of more complex pagination (adding extra arguments) by adding a global 'get_pagination_html' - Allow follow-up of ChatSession / Specialist execution - Improvement in logging of Specialists (but needs to be finished)
This commit is contained in:
@@ -81,6 +81,8 @@ class DynamicFormBase(FlaskForm):
|
||||
message=f"Value must be between {min_value or '-∞'} and {max_value or '∞'}"
|
||||
)
|
||||
)
|
||||
elif field_type in ['string', 'str']:
|
||||
validators_list.append(self._validate_string_not_empty)
|
||||
elif field_type == 'tagging_fields':
|
||||
validators_list.append(self._validate_tagging_fields)
|
||||
elif field_type == 'tagging_fields_filter':
|
||||
@@ -130,6 +132,11 @@ class DynamicFormBase(FlaskForm):
|
||||
except Exception as e:
|
||||
raise ValidationError(f"Invalid filter definition: {str(e)}")
|
||||
|
||||
def _validate_string_not_empty(self, form, field):
|
||||
"""Validator om te controleren of een StringField niet leeg is"""
|
||||
if not field.data or field.data.strip() == "":
|
||||
raise ValidationError("This field cannot be empty")
|
||||
|
||||
def _validate_filter_condition(self, condition):
|
||||
"""Recursively validate a filter condition structure"""
|
||||
# Check if this is a logical condition (AND/OR/NOT)
|
||||
@@ -264,6 +271,7 @@ class DynamicFormBase(FlaskForm):
|
||||
'float': FloatField,
|
||||
'boolean': BooleanField,
|
||||
'string': StringField,
|
||||
'str': StringField,
|
||||
'text': TextAreaField,
|
||||
'date': DateField,
|
||||
'file': FileField,
|
||||
@@ -368,6 +376,28 @@ class DynamicFormBase(FlaskForm):
|
||||
data[original_field_name] = field.data
|
||||
return data
|
||||
|
||||
# def validate_on_submit(self):
|
||||
# """Aangepaste validate_on_submit die dynamische velden correct verwerkt"""
|
||||
# if request.method == 'POST':
|
||||
# # Update formdata met de huidige request data
|
||||
# self.formdata = request.form
|
||||
# self.raw_formdata = request.form.to_dict()
|
||||
#
|
||||
# # Verwerk alle dynamische velden opnieuw met de actuele formuliergegevens
|
||||
# for collection_name, field_names in self.dynamic_fields.items():
|
||||
# for full_field_name in field_names:
|
||||
# field = self._fields.get(full_field_name)
|
||||
# if field:
|
||||
# # Log voor debug
|
||||
# current_app.logger.debug(
|
||||
# f"Re-processing dynamic field {full_field_name} with current form data")
|
||||
# # Verwerk het veld opnieuw met de huidige request data
|
||||
# field.process(self.formdata)
|
||||
#
|
||||
# # Nu voeren we de standaard validatie uit
|
||||
# return super().validate()
|
||||
# return False
|
||||
|
||||
|
||||
def validate_tagging_fields(form, field):
|
||||
"""Validate the tagging fields structure"""
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (StringField, BooleanField, SelectField, TextAreaField)
|
||||
from wtforms.fields.datetime import DateField
|
||||
from wtforms.fields.numeric import IntegerField
|
||||
from wtforms.validators import DataRequired, Length, Optional
|
||||
|
||||
from wtforms_sqlalchemy.fields import QuerySelectMultipleField
|
||||
@@ -124,3 +125,11 @@ class EditEveAIAssetVersionForm(DynamicFormBase):
|
||||
asset_type_version = StringField('Asset Type Version', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
bucket_name = StringField('Bucket Name', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
|
||||
|
||||
class ExecuteSpecialistForm(DynamicFormBase):
|
||||
id = IntegerField('Specialist ID', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
name = StringField('Specialist Name', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
description = TextAreaField('Specialist Description', validators=[Optional()], render_kw={'readonly': True})
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import ast
|
||||
import json
|
||||
from datetime import datetime as dt, timezone as tz
|
||||
import time
|
||||
|
||||
from flask import request, redirect, flash, render_template, Blueprint, session, current_app, jsonify, url_for
|
||||
from flask_security import roles_accepted
|
||||
@@ -14,7 +16,9 @@ from common.models.interaction import (ChatSession, Interaction, InteractionEmbe
|
||||
EveAIAgent, EveAITask, EveAITool, EveAIAssetVersion)
|
||||
|
||||
from common.extensions import db, cache_manager
|
||||
from common.services.interaction.specialist_services import SpecialistServices
|
||||
from common.utils.asset_utils import create_asset_stack, add_asset_version_file
|
||||
from common.utils.execution_progress import ExecutionProgressTracker
|
||||
from common.utils.model_logging_utils import set_logging_information, update_logging_information
|
||||
|
||||
from common.utils.middleware import mw_before_request
|
||||
@@ -25,7 +29,7 @@ from common.utils.specialist_utils import initialize_specialist
|
||||
from config.type_defs.specialist_types import SPECIALIST_TYPES
|
||||
|
||||
from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAgentForm, EditEveAITaskForm,
|
||||
EditEveAIToolForm, AddEveAIAssetForm, EditEveAIAssetVersionForm)
|
||||
EditEveAIToolForm, AddEveAIAssetForm, EditEveAIAssetVersionForm, ExecuteSpecialistForm)
|
||||
|
||||
interaction_bp = Blueprint('interaction_bp', __name__, url_prefix='/interaction')
|
||||
|
||||
@@ -72,10 +76,13 @@ def handle_chat_session_selection():
|
||||
cs_id = ast.literal_eval(chat_session_identification).get('value')
|
||||
|
||||
action = request.form['action']
|
||||
current_app.logger.debug(f'Handle Chat Session Selection Action: {action}')
|
||||
|
||||
match action:
|
||||
case 'view_chat_session':
|
||||
return redirect(prefixed_url_for('interaction_bp.view_chat_session', chat_session_id=cs_id))
|
||||
case 'chat_session_interactions':
|
||||
return redirect(prefixed_url_for('interaction_bp.session_interactions', chat_session_id=cs_id))
|
||||
|
||||
# Add more conditions for other actions
|
||||
return redirect(prefixed_url_for('interaction_bp.chat_sessions'))
|
||||
@@ -124,8 +131,14 @@ def view_chat_session(chat_session_id):
|
||||
@interaction_bp.route('/view_chat_session_by_session_id/<session_id>', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def view_chat_session_by_session_id(session_id):
|
||||
"""
|
||||
Deze route accepteert een session_id (string) en stuurt door naar view_chat_session met het juiste chat_session_id (integer)
|
||||
"""
|
||||
# Vind de chat session op basis van session_id
|
||||
chat_session = ChatSession.query.filter_by(session_id=session_id).first_or_404()
|
||||
show_chat_session(chat_session)
|
||||
|
||||
# Nu we het chat_session object hebben, kunnen we de bestaande functie hergebruiken
|
||||
return view_chat_session(chat_session.id)
|
||||
|
||||
|
||||
def show_chat_session(chat_session):
|
||||
@@ -303,6 +316,8 @@ def handle_specialist_selection():
|
||||
|
||||
if action == "edit_specialist":
|
||||
return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=specialist_id))
|
||||
elif action == "execute_specialist":
|
||||
return redirect(prefixed_url_for('interaction_bp.execute_specialist', specialist_id=specialist_id))
|
||||
|
||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||
|
||||
@@ -391,9 +406,8 @@ def edit_tool(tool_id):
|
||||
form = EditEveAIToolForm(obj=tool)
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return render_template('interaction/components/edit_tool.html',
|
||||
form=form,
|
||||
tool=tool)
|
||||
return render_template('interaction/components/edit_tool.html', form=form, tool=tool)
|
||||
return None
|
||||
|
||||
|
||||
@interaction_bp.route('/tool/<int:tool_id>/save', methods=['POST'])
|
||||
@@ -546,3 +560,106 @@ def edit_asset_version(asset_version_id):
|
||||
return render_template('interaction/edit_asset_version.html', form=form)
|
||||
|
||||
|
||||
@interaction_bp.route('/execute_specialist/<int:specialist_id>', methods=['GET', 'POST'])
|
||||
def execute_specialist(specialist_id):
|
||||
specialist = Specialist.query.get_or_404(specialist_id)
|
||||
specialist_config = cache_manager.specialists_config_cache.get_config(specialist.type, specialist.type_version)
|
||||
|
||||
if specialist_config.get('chat', True):
|
||||
flash("Only specialists that don't require interactions can be executed this way!", 'error')
|
||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||
|
||||
form = ExecuteSpecialistForm(request.form, obj=specialist)
|
||||
arguments_config = specialist_config.get('arguments', None)
|
||||
if arguments_config:
|
||||
form.add_dynamic_fields('arguments', arguments_config)
|
||||
|
||||
if form.validate_on_submit():
|
||||
# We're only interested in gathering the dynamic arguments
|
||||
arguments = form.get_dynamic_data("arguments")
|
||||
current_app.logger.debug(f"Executing specialist {specialist.id} with arguments: {arguments}")
|
||||
session_id = SpecialistServices.start_session()
|
||||
execution_task = SpecialistServices.execute_specialist(
|
||||
tenant_id=session.get('tenant').get('id'),
|
||||
specialist_id=specialist_id,
|
||||
specialist_arguments=arguments,
|
||||
session_id=session_id,
|
||||
user_timezone=session.get('tenant').get('timezone')
|
||||
)
|
||||
current_app.logger.debug(f"Execution task for specialist {specialist.id} created: {execution_task}")
|
||||
return redirect(prefixed_url_for('interaction_bp.session_interactions_by_session_id', session_id=session_id))
|
||||
|
||||
return render_template('interaction/execute_specialist.html', form=form)
|
||||
|
||||
|
||||
@interaction_bp.route('/session_interactions_by_session_id/<session_id>', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def session_interactions_by_session_id(session_id):
|
||||
"""
|
||||
This route shows all interactions for a given session_id (string).
|
||||
If the chat_session doesn't exist yet, it will wait for up to 10 seconds
|
||||
(with 1 second intervals) until it becomes available.
|
||||
"""
|
||||
waiting_message = request.args.get('waiting', 'false') == 'true'
|
||||
|
||||
# Try up to 10 times with 1 second pause
|
||||
max_tries = 10
|
||||
current_try = 1
|
||||
|
||||
while current_try <= max_tries:
|
||||
chat_session = ChatSession.query.filter_by(session_id=session_id).first()
|
||||
|
||||
if chat_session:
|
||||
# Session found, display the interactions
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
|
||||
query = Interaction.query.filter_by(chat_session_id=chat_session.id).order_by(Interaction.question_at)
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
interactions = pagination.items
|
||||
|
||||
rows = prepare_table_for_macro(interactions, [('id', ''), ('question_at', ''), ('detailed_question_at', ''),
|
||||
('answer_at', ''), ('processing_error', '')])
|
||||
|
||||
# Define a callback to make a URL for a given page and the same session_id
|
||||
def make_page_url(page_num):
|
||||
return prefixed_url_for('interaction_bp.session_interactions_by_session_id', session_id=session_id,
|
||||
page=page_num)
|
||||
|
||||
return render_template('interaction/session_interactions.html',
|
||||
chat_session=chat_session, rows=rows, pagination=pagination,
|
||||
make_page_url=make_page_url)
|
||||
|
||||
# Session not found, wait and try again
|
||||
if current_try < max_tries:
|
||||
current_try += 1
|
||||
time.sleep(1)
|
||||
else:
|
||||
# Maximum number of attempts reached
|
||||
break
|
||||
|
||||
# If we're here, the session wasn't found after the maximum number of attempts
|
||||
flash(f'The chat session with ID {session_id} could not be found after {max_tries} attempts. '
|
||||
f'The session may still be in the process of being created or the ID might be incorrect.', 'warning')
|
||||
|
||||
# Show a waiting page with auto-refresh if we haven't shown a waiting message yet
|
||||
if not waiting_message:
|
||||
return render_template('interaction/waiting_for_session.html',
|
||||
session_id=session_id,
|
||||
refresh_url=prefixed_url_for('interaction_bp.session_interactions_by_session_id',
|
||||
session_id=session_id,
|
||||
waiting='true'))
|
||||
|
||||
# If we've already shown a waiting message and still don't have a session, go back to the specialists page
|
||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||
|
||||
|
||||
@interaction_bp.route('/session_interactions/<chat_session_id>', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def session_interactions(chat_session_id):
|
||||
"""
|
||||
This route shows all interactions for a given chat_session_id (int).
|
||||
"""
|
||||
chat_session = ChatSession.query.get_or_404(chat_session_id)
|
||||
return session_interactions_by_session_id(chat_session.session_id)
|
||||
@@ -163,22 +163,24 @@ def edit_partner_service(partner_service_id):
|
||||
partner_id = session['partner']['id']
|
||||
|
||||
form = EditPartnerServiceForm(obj=partner_service)
|
||||
partner_service_config = cache_manager.partner_services_config_cache.get_config(partner_service.type,
|
||||
partner_service.type_version)
|
||||
configuration_config = partner_service_config.get('configuration')
|
||||
current_app.logger.debug(f"Configuration config for {partner_service.type} {partner_service.type_version}: "
|
||||
f"{configuration_config}")
|
||||
form.add_dynamic_fields("configuration", configuration_config, partner_service.configuration)
|
||||
permissions_config = partner_service_config.get('permissions')
|
||||
current_app.logger.debug(f"Permissions config for {partner_service.type} {partner_service.type_version}: "
|
||||
f"{permissions_config}")
|
||||
form.add_dynamic_fields("permissions", permissions_config, partner_service.permissions)
|
||||
if request.method == 'GET':
|
||||
partner_service_config = cache_manager.partner_services_config_cache.get_config(partner_service.type,
|
||||
partner_service.type_version)
|
||||
configuration_config = partner_service_config.get('configuration')
|
||||
current_app.logger.debug(f"Configuration config for {partner_service.type} {partner_service.type_version}: "
|
||||
f"{configuration_config}")
|
||||
form.add_dynamic_fields("configuration", configuration_config, partner_service.configuration)
|
||||
permissions_config = partner_service_config.get('permissions')
|
||||
current_app.logger.debug(f"Permissions config for {partner_service.type} {partner_service.type_version}: "
|
||||
f"{permissions_config}")
|
||||
form.add_dynamic_fields("permissions", permissions_config, partner_service.permissions)
|
||||
|
||||
if form.validate_on_submit():
|
||||
if request.method == 'POST':
|
||||
current_app.logger.debug(f"Form returned: {form.data}")
|
||||
raw_form_data = request.form.to_dict()
|
||||
current_app.logger.debug(f"Raw form data: {raw_form_data}")
|
||||
|
||||
if form.validate_on_submit():
|
||||
form.populate_obj(partner_service)
|
||||
partner_service.configuration = form.get_dynamic_data('configuration')
|
||||
partner_service.permissions = form.get_dynamic_data('permissions')
|
||||
|
||||
@@ -37,8 +37,6 @@ class TenantForm(FlaskForm):
|
||||
self.currency.choices = [(curr, curr) for curr in current_app.config['SUPPORTED_CURRENCIES']]
|
||||
# initialise timezone
|
||||
self.timezone.choices = [(tz, tz) for tz in pytz.all_timezones]
|
||||
# initialise LLM fields
|
||||
self.llm_model.choices = [(model, model) for model in current_app.config['SUPPORTED_LLMS']]
|
||||
# Initialize fallback algorithms
|
||||
self.type.choices = [(t, t) for t in current_app.config['TENANT_TYPES']]
|
||||
# Show field only for Super Users with partner in session
|
||||
|
||||
@@ -302,7 +302,6 @@ def handle_tenant_selection():
|
||||
# set tenant information in the session
|
||||
session['tenant'] = the_tenant.to_dict()
|
||||
session['default_language'] = the_tenant.default_language
|
||||
session['llm_model'] = the_tenant.llm_model
|
||||
# remove catalog-related items from the session
|
||||
session.pop('catalog_id', None)
|
||||
session.pop('catalog_name', None)
|
||||
|
||||
Reference in New Issue
Block a user