- Fixed Error where Catalog Types other than default could not be added
- Fixed error in TRAICIE_KO_INTERVIEW_DEFINITION_SPECIALIST - Minor improvements
This commit is contained in:
@@ -16,7 +16,7 @@ backstory: >
|
|||||||
AI-driven sourcing. You’re more than a recruiter—you’re a trusted advisor, a brand ambassador, and a connector of
|
AI-driven sourcing. You’re more than a recruiter—you’re a trusted advisor, a brand ambassador, and a connector of
|
||||||
people and purpose.
|
people and purpose.
|
||||||
{custom_backstory}
|
{custom_backstory}
|
||||||
full_model_name: "mistral.magistral-medium-latest"
|
full_model_name: "mistral.mistral-medium-latest"
|
||||||
temperature: 0.3
|
temperature: 0.3
|
||||||
metadata:
|
metadata:
|
||||||
author: "Josako"
|
author: "Josako"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ backstory: >
|
|||||||
AI-driven sourcing. You’re more than a recruiter—you’re a trusted advisor, a brand ambassador, and a connector of
|
AI-driven sourcing. You’re more than a recruiter—you’re a trusted advisor, a brand ambassador, and a connector of
|
||||||
people and purpose.
|
people and purpose.
|
||||||
{custom_backstory}
|
{custom_backstory}
|
||||||
full_model_name: "mistral.magistral-medium-latest"
|
full_model_name: "mistral.mistral-medium-latest"
|
||||||
temperature: 0.3
|
temperature: 0.3
|
||||||
metadata:
|
metadata:
|
||||||
author: "Josako"
|
author: "Josako"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ task_description: >
|
|||||||
|
|
||||||
- A short (1 sentence), closed-ended question (Yes / No) to ask the recruitment candidate. Use your experience to ask a question that
|
- A short (1 sentence), closed-ended question (Yes / No) to ask the recruitment candidate. Use your experience to ask a question that
|
||||||
enables us to verify compliancy to the criterium.
|
enables us to verify compliancy to the criterium.
|
||||||
- A set of 2 short answers (1 small sentence each) to that question (positive answer / negative answer), from the
|
- A set of 2 short answers (1 small sentence of about 10 words each) to that question (positive answer / negative answer), from the
|
||||||
candidates perspective.
|
candidates perspective. Do not just repeat the words already formulated in the question.
|
||||||
The positive answer will result in a positive evaluation of the criterium, the negative answer in a negative evaluation
|
The positive answer will result in a positive evaluation of the criterium, the negative answer in a negative evaluation
|
||||||
of the criterium. Try to avoid just using Yes / No as positive and negative answers.
|
of the criterium. Try to avoid just using Yes / No as positive and negative answers.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ CATALOG_TYPES = {
|
|||||||
"name": "Standard Catalog",
|
"name": "Standard Catalog",
|
||||||
"description": "A Catalog with information in Evie's Library, to be considered as a whole",
|
"description": "A Catalog with information in Evie's Library, to be considered as a whole",
|
||||||
},
|
},
|
||||||
"TRAICIE_RQC": {
|
"TRAICIE_ROLE_DEFINITION_CATALOG": {
|
||||||
"name": "Role Definition Catalog",
|
"name": "Role Definition Catalog",
|
||||||
"description": "A Catalog with information about roles, to be considered as a whole",
|
"description": "A Catalog with information about roles, to be considered as a whole",
|
||||||
"partner": "traicie"
|
"partner": "traicie"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<p><strong>Type Version:</strong> {{ asset.type_version }}</p>
|
<p><strong>Type Version:</strong> {{ asset.type_version }}</p>
|
||||||
<p><strong>File Type:</strong> {{ asset.file_type }}</p>
|
<p><strong>File Type:</strong> {{ asset.file_type }}</p>
|
||||||
<p><strong>File Size:</strong> {{ asset.file_size or 'N/A' }} bytes</p>
|
<p><strong>File Size:</strong> {{ asset.file_size or 'N/A' }} MiB</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% from "macros.html" import render_field %}
|
||||||
|
|
||||||
|
{% block title %}Specialist Magic Link URLs{% endblock %}
|
||||||
|
|
||||||
|
{% block content_title %}Specialist Magic Link URLs{% endblock %}
|
||||||
|
{% block content_description %}View URL and QR Code for a Magic Link{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{% set disabled_fields = [] %}
|
||||||
|
{% set exclude_fields = [] %}
|
||||||
|
{% for field in form %}
|
||||||
|
{% if field.name == 'qr_code_url' and field.data %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ field.id }}">{{ field.label.text }}</label>
|
||||||
|
<div style="max-width: 200px;">
|
||||||
|
<img src="{{ field.data }}" alt="QR Code" class="img-fluid">
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="{{ field.name }}" value="{{ field.data|e }}">
|
||||||
|
</div>
|
||||||
|
{% elif field.name == 'chat_client_url' %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ field.id }}" class="form-label">{{ field.label.text }}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" value="{{ field.data }}" id="{{ field.id }}" readonly>
|
||||||
|
<a href="{{ field.data }}" class="btn btn-primary" target="_blank">Open link</a>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="{{ field.name }}" value="{{ field.data|e }}">
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content_footer %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -392,7 +392,7 @@ def add_document():
|
|||||||
|
|
||||||
flash(f'Processing on document {new_doc.name}, version {new_doc_vers.id} started. Task ID: {task_id}.',
|
flash(f'Processing on document {new_doc.name}, version {new_doc_vers.id} started. Task ID: {task_id}.',
|
||||||
'success')
|
'success')
|
||||||
return redirect(prefixed_url_for('document_bp.documents'))
|
return redirect(prefixed_url_for('document_bp.documents_processing'))
|
||||||
|
|
||||||
except EveAIException as e:
|
except EveAIException as e:
|
||||||
flash(str(e), 'error')
|
flash(str(e), 'error')
|
||||||
@@ -451,7 +451,7 @@ def add_url():
|
|||||||
|
|
||||||
flash(f'Processing on document {new_doc.name}, version {new_doc_vers.id} started. Task ID: {task_id}.',
|
flash(f'Processing on document {new_doc.name}, version {new_doc_vers.id} started. Task ID: {task_id}.',
|
||||||
'success')
|
'success')
|
||||||
return redirect(prefixed_url_for('document_bp.documents'))
|
return redirect(prefixed_url_for('document_bp.documents_processing'))
|
||||||
|
|
||||||
except EveAIException as e:
|
except EveAIException as e:
|
||||||
current_app.logger.error(f"Error adding document: {str(e)}")
|
current_app.logger.error(f"Error adding document: {str(e)}")
|
||||||
|
|||||||
@@ -134,8 +134,7 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
|
|||||||
render_kw={'readonly': True})
|
render_kw={'readonly': True})
|
||||||
specialist_id = IntegerField('Specialist', validators=[DataRequired()], render_kw={'readonly': True})
|
specialist_id = IntegerField('Specialist', validators=[DataRequired()], render_kw={'readonly': True})
|
||||||
specialist_name = StringField('Specialist Name', validators=[DataRequired()], render_kw={'readonly': True})
|
specialist_name = StringField('Specialist Name', validators=[DataRequired()], render_kw={'readonly': True})
|
||||||
chat_client_url = StringField('Chat Client URL', validators=[Optional()], render_kw={'readonly': True})
|
|
||||||
qr_code_url = StringField('QR Code', validators=[Optional()], render_kw={'readonly': True})
|
|
||||||
tenant_make_id = SelectField('Tenant Make', validators=[Optional()], coerce=int)
|
tenant_make_id = SelectField('Tenant Make', validators=[Optional()], coerce=int)
|
||||||
valid_from = DateField('Valid From', id='form-control datepicker', validators=[Optional()])
|
valid_from = DateField('Valid From', id='form-control datepicker', validators=[Optional()])
|
||||||
valid_to = DateField('Valid To', id='form-control datepicker', validators=[Optional()])
|
valid_to = DateField('Valid To', id='form-control datepicker', validators=[Optional()])
|
||||||
@@ -158,5 +157,14 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
|
|||||||
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
|
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
|
||||||
|
|
||||||
|
|
||||||
|
class ViewSpecialistMagicLinkURLsForm(FlaskForm):
|
||||||
|
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||||
|
description = TextAreaField('Description', validators=[Optional()])
|
||||||
|
magic_link_code = StringField('Magic Link Code', validators=[DataRequired(), Length(max=55)], render_kw={'readonly': True})
|
||||||
|
|
||||||
|
chat_client_url = StringField('Chat Client URL', validators=[Optional()], render_kw={'readonly': True})
|
||||||
|
qr_code_url = StringField('QR Code', validators=[Optional()], render_kw={'readonly': True})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from common.utils.view_assistants import form_validation_failed, prepare_table_f
|
|||||||
|
|
||||||
from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAgentForm, EditEveAITaskForm,
|
from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAgentForm, EditEveAITaskForm,
|
||||||
EditEveAIToolForm, ExecuteSpecialistForm,
|
EditEveAIToolForm, ExecuteSpecialistForm,
|
||||||
SpecialistMagicLinkForm, EditSpecialistMagicLinkForm)
|
SpecialistMagicLinkForm, EditSpecialistMagicLinkForm, ViewSpecialistMagicLinkURLsForm)
|
||||||
|
|
||||||
from eveai_app.views.list_views.interaction_list_views import (get_specialists_list_view, get_assets_list_view,
|
from eveai_app.views.list_views.interaction_list_views import (get_specialists_list_view, get_assets_list_view,
|
||||||
get_magic_links_list_view, get_chat_sessions_list_view,
|
get_magic_links_list_view, get_chat_sessions_list_view,
|
||||||
@@ -648,52 +648,6 @@ def edit_specialist_magic_link(specialist_magic_link_id):
|
|||||||
else:
|
else:
|
||||||
form.tenant_make_id.data = specialist_ml.tenant_make_id
|
form.tenant_make_id.data = specialist_ml.tenant_make_id
|
||||||
|
|
||||||
# Set the chat client URL
|
|
||||||
tenant_id = session.get('tenant').get('id')
|
|
||||||
chat_client_prefix = current_app.config.get('CHAT_CLIENT_PREFIX', 'chat_client/chat/')
|
|
||||||
base_url = request.url_root
|
|
||||||
magic_link_code = specialist_ml.magic_link_code
|
|
||||||
|
|
||||||
# Parse the URL om poortinformatie te behouden als deze afwijkt van de standaard
|
|
||||||
url_parts = request.url.split('/')
|
|
||||||
host_port = url_parts[2] # Dit bevat zowel hostname als poort indien aanwezig
|
|
||||||
|
|
||||||
# Generate the full URL for chat client with magic link code
|
|
||||||
chat_client_url = f"{request.scheme}://{host_port}/{chat_client_prefix}{magic_link_code}"
|
|
||||||
form.chat_client_url.data = chat_client_url
|
|
||||||
|
|
||||||
# Generate QR code as data URI for direct embedding in HTML
|
|
||||||
try:
|
|
||||||
import qrcode
|
|
||||||
import io
|
|
||||||
import base64
|
|
||||||
|
|
||||||
# Generate QR code as PNG for better compatibility
|
|
||||||
qr = qrcode.QRCode(
|
|
||||||
version=1,
|
|
||||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
|
||||||
box_size=10,
|
|
||||||
border=4
|
|
||||||
)
|
|
||||||
qr.add_data(chat_client_url)
|
|
||||||
qr.make(fit=True)
|
|
||||||
|
|
||||||
# Generate PNG image in memory
|
|
||||||
img = qr.make_image(fill_color="black", back_color="white")
|
|
||||||
buffer = io.BytesIO()
|
|
||||||
img.save(buffer, format='PNG')
|
|
||||||
img_data = buffer.getvalue()
|
|
||||||
|
|
||||||
# Create data URI for direct embedding in HTML
|
|
||||||
img_base64 = base64.b64encode(img_data).decode('utf-8')
|
|
||||||
data_uri = f"data:image/png;base64,{img_base64}"
|
|
||||||
|
|
||||||
# Store the data URI in the form data
|
|
||||||
form.qr_code_url.data = data_uri
|
|
||||||
except Exception as e:
|
|
||||||
current_app.logger.error(f"Failed to generate QR code: {str(e)}")
|
|
||||||
form.qr_code_url.data = "Error generating QR code"
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
# Update the basic fields
|
# Update the basic fields
|
||||||
form.populate_obj(specialist_ml)
|
form.populate_obj(specialist_ml)
|
||||||
@@ -722,6 +676,61 @@ def edit_specialist_magic_link(specialist_magic_link_id):
|
|||||||
return render_template('interaction/edit_specialist_magic_link.html', form=form)
|
return render_template('interaction/edit_specialist_magic_link.html', form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@interaction_bp.route('/view_specialist_magic_link_urls/<int:specialist_magic_link_id>', methods=['GET'])
|
||||||
|
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||||
|
def view_specialist_magic_link_urls(specialist_magic_link_id):
|
||||||
|
specialist_ml = SpecialistMagicLink.query.get_or_404(specialist_magic_link_id)
|
||||||
|
form = ViewSpecialistMagicLinkURLsForm(obj=specialist_ml)
|
||||||
|
|
||||||
|
# Set the chat client URL
|
||||||
|
tenant_id = session.get('tenant').get('id')
|
||||||
|
chat_client_prefix = current_app.config.get('CHAT_CLIENT_PREFIX', 'chat_client/chat/')
|
||||||
|
base_url = request.url_root
|
||||||
|
magic_link_code = specialist_ml.magic_link_code
|
||||||
|
|
||||||
|
# Parse the URL om poortinformatie te behouden als deze afwijkt van de standaard
|
||||||
|
url_parts = request.url.split('/')
|
||||||
|
host_port = url_parts[2] # Dit bevat zowel hostname als poort indien aanwezig
|
||||||
|
|
||||||
|
# Generate the full URL for chat client with magic link code
|
||||||
|
chat_client_url = f"{request.scheme}://{host_port}/{chat_client_prefix}{magic_link_code}"
|
||||||
|
form.chat_client_url.data = chat_client_url
|
||||||
|
|
||||||
|
# Generate QR code as data URI for direct embedding in HTML
|
||||||
|
try:
|
||||||
|
import qrcode
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# Generate QR code as PNG for better compatibility
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1,
|
||||||
|
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||||
|
box_size=10,
|
||||||
|
border=4
|
||||||
|
)
|
||||||
|
qr.add_data(chat_client_url)
|
||||||
|
qr.make(fit=True)
|
||||||
|
|
||||||
|
# Generate PNG image in memory
|
||||||
|
img = qr.make_image(fill_color="black", back_color="white")
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
img.save(buffer, format='PNG')
|
||||||
|
img_data = buffer.getvalue()
|
||||||
|
|
||||||
|
# Create data URI for direct embedding in HTML
|
||||||
|
img_base64 = base64.b64encode(img_data).decode('utf-8')
|
||||||
|
data_uri = f"data:image/png;base64,{img_base64}"
|
||||||
|
|
||||||
|
# Store the data URI in the form data
|
||||||
|
form.qr_code_url.data = data_uri
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"Failed to generate QR code: {str(e)}")
|
||||||
|
form.qr_code_url.data = "Error generating QR code"
|
||||||
|
|
||||||
|
return render_template('interaction/view_specialist_magic_link_urls.html', form=form)
|
||||||
|
|
||||||
|
|
||||||
@interaction_bp.route('/specialist_magic_links', methods=['GET', 'POST'])
|
@interaction_bp.route('/specialist_magic_links', methods=['GET', 'POST'])
|
||||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||||
def specialist_magic_links():
|
def specialist_magic_links():
|
||||||
@@ -743,6 +752,9 @@ def handle_specialist_magic_link_selection():
|
|||||||
if action == "edit_specialist_magic_link":
|
if action == "edit_specialist_magic_link":
|
||||||
return redirect(prefixed_url_for('interaction_bp.edit_specialist_magic_link',
|
return redirect(prefixed_url_for('interaction_bp.edit_specialist_magic_link',
|
||||||
specialist_magic_link_id=specialist_ml_id))
|
specialist_magic_link_id=specialist_ml_id))
|
||||||
|
if action == "view_specialist_magic_link_urls":
|
||||||
|
return redirect(prefixed_url_for('interaction_bp.view_specialist_magic_link_urls',
|
||||||
|
specialist_magic_link_id=specialist_ml_id))
|
||||||
|
|
||||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ def get_magic_links_list_view():
|
|||||||
# Action definitions
|
# Action definitions
|
||||||
actions = [
|
actions = [
|
||||||
{'value': 'edit_specialist_magic_link', 'text': 'Edit Magic Link', 'class': 'btn-primary', 'requiresSelection': True},
|
{'value': 'edit_specialist_magic_link', 'text': 'Edit Magic Link', 'class': 'btn-primary', 'requiresSelection': True},
|
||||||
{'value': 'create_specialist_magic_link', 'text': 'Create Magic Link', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
|
{'value': 'view_specialist_magic_link_urls', 'text': 'View Magic Link URLs', 'class': 'btn-secondary', 'requiresSelection': True},
|
||||||
|
{'value': 'create_specialist_magic_link', 'text': 'Create Magic Link', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Initial sort configuration
|
# Initial sort configuration
|
||||||
|
|||||||
@@ -43,3 +43,11 @@ class KOQuestions(BaseModel):
|
|||||||
"""Get the list of KOQuestion objects"""
|
"""Get the list of KOQuestion objects"""
|
||||||
return self.ko_questions
|
return self.ko_questions
|
||||||
|
|
||||||
|
def get_by_title(self, title: str) -> Optional[KOQuestion]:
|
||||||
|
return next((q for q in self.ko_questions if q.title == title), None)
|
||||||
|
|
||||||
|
def get_next_by_title(self, title: str) -> Optional[KOQuestion]:
|
||||||
|
for idx, q in enumerate(self.ko_questions):
|
||||||
|
if q.title == title:
|
||||||
|
return self.ko_questions[idx + 1] if idx + 1 < len(self.ko_questions) else None
|
||||||
|
return None
|
||||||
@@ -152,6 +152,7 @@ class CrewAIBaseSpecialistExecutor(BaseSpecialistExecutor):
|
|||||||
agent_backstory = agent_config.get('backstory', '').replace('{custom_backstory}', agent.backstory or '')
|
agent_backstory = agent_config.get('backstory', '').replace('{custom_backstory}', agent.backstory or '')
|
||||||
agent_backstory = self._replace_system_variables(agent_backstory)
|
agent_backstory = self._replace_system_variables(agent_backstory)
|
||||||
agent_full_model_name = agent_config.get('full_model_name', 'mistral.mistral-large-latest')
|
agent_full_model_name = agent_config.get('full_model_name', 'mistral.mistral-large-latest')
|
||||||
|
current_app.logger.debug(f"Full model name for {agent.type}: {agent_full_model_name}")
|
||||||
agent_temperature = agent_config.get('temperature', 0.3)
|
agent_temperature = agent_config.get('temperature', 0.3)
|
||||||
llm = get_crewai_llm(agent_full_model_name, agent_temperature)
|
llm = get_crewai_llm(agent_full_model_name, agent_temperature)
|
||||||
if not llm:
|
if not llm:
|
||||||
@@ -331,6 +332,7 @@ class CrewAIBaseSpecialistExecutor(BaseSpecialistExecutor):
|
|||||||
for state_name, result_name in self._state_result_relations.items():
|
for state_name, result_name in self._state_result_relations.items():
|
||||||
if result_name in last_interaction.specialist_results:
|
if result_name in last_interaction.specialist_results:
|
||||||
setattr(self.flow.state, state_name, last_interaction.specialist_results[result_name])
|
setattr(self.flow.state, state_name, last_interaction.specialist_results[result_name])
|
||||||
|
#TODO: Hier wordt steeds een dict of json terug gegeven, geen pydantic model?
|
||||||
|
|
||||||
# Initialize the standard state values
|
# Initialize the standard state values
|
||||||
self.flow.state.answer = None
|
self.flow.state.answer = None
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import random
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
@@ -33,6 +34,17 @@ KO_CRITERIA_NOT_MET_MESSAGE = ("Thank you for answering our questions! We proces
|
|||||||
"not comply with the minimum requirements for this job. Therefor, we stop this"
|
"not comply with the minimum requirements for this job. Therefor, we stop this"
|
||||||
"selection procedure")
|
"selection procedure")
|
||||||
KO_CRITERIA_MET_MESSAGE = "We processed your answers with a positive result."
|
KO_CRITERIA_MET_MESSAGE = "We processed your answers with a positive result."
|
||||||
|
KO_CRITERIA_NEXT_MESSAGES = [
|
||||||
|
"Thank you for your answer. Here's a next question.",
|
||||||
|
"Your answer fits our needs. We have yet another question to ask you.",
|
||||||
|
"Positive this far! Here's a follow-up question.",
|
||||||
|
"Great, that’s just what we were hoping for. Let’s continue with another question.",
|
||||||
|
"Appreciate your reply! Here's the next one.",
|
||||||
|
"Thanks for the input. Let’s move on to the next question.",
|
||||||
|
"That’s exactly what we needed to hear. Here comes the next question.",
|
||||||
|
"Looks promising! Let’s continue with another quick check.",
|
||||||
|
"Thanks! Here's another point we'd like to clarify."
|
||||||
|
]
|
||||||
RQC_MESSAGE = "You are well suited for this job."
|
RQC_MESSAGE = "You are well suited for this job."
|
||||||
CONTACT_DATA_QUESTION = ("Are you willing to provide us with your contact data, so we can contact you to continue "
|
CONTACT_DATA_QUESTION = ("Are you willing to provide us with your contact data, so we can contact you to continue "
|
||||||
"the selection process?")
|
"the selection process?")
|
||||||
@@ -83,10 +95,9 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
|
|
||||||
def _config_state_result_relations(self):
|
def _config_state_result_relations(self):
|
||||||
self._add_state_result_relation("rag_output")
|
self._add_state_result_relation("rag_output")
|
||||||
self._add_state_result_relation("ko_criteria_questions")
|
self._add_state_result_relation("ko_criteria_scores")
|
||||||
self._add_state_result_relation("ko_criteria_answers")
|
self._add_state_result_relation("current_ko_criterium")
|
||||||
self._add_state_result_relation("competency_questions")
|
self._add_state_result_relation("current_ko_criterium_idx")
|
||||||
self._add_state_result_relation("competency_scores")
|
|
||||||
self._add_state_result_relation("personal_contact_data")
|
self._add_state_result_relation("personal_contact_data")
|
||||||
self._add_state_result_relation("contact_time_prefs")
|
self._add_state_result_relation("contact_time_prefs")
|
||||||
|
|
||||||
@@ -175,25 +186,10 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
answer = initialisation_message
|
answer = initialisation_message
|
||||||
|
|
||||||
ko_questions = self._get_ko_questions()
|
ko_questions = self._get_ko_questions()
|
||||||
fields = {}
|
|
||||||
for ko_question in ko_questions.ko_questions:
|
|
||||||
fields[ko_question.title] = {
|
|
||||||
"name": ko_question.title,
|
|
||||||
"description": ko_question.title,
|
|
||||||
"context": ko_question.question,
|
|
||||||
"type": "options",
|
|
||||||
"required": True,
|
|
||||||
"allowed_values": [ko_question.answer_positive, ko_question.answer_negative]
|
|
||||||
}
|
|
||||||
|
|
||||||
ko_form = {
|
current_ko_criterium = ko_questions.ko_questions[0].title
|
||||||
"type": "KO_CRITERIA_FORM",
|
current_ko_criterium_idx = 0
|
||||||
"version": "1.0.0",
|
ko_form = self._prepare_ko_question_form(ko_questions, current_ko_criterium, arguments.language)
|
||||||
"name": "Starter Questions",
|
|
||||||
"icon": "verified",
|
|
||||||
"fields": fields,
|
|
||||||
}
|
|
||||||
ko_form = TranslationServices.translate_config(self.tenant_id, ko_form, "fields", arguments.language)
|
|
||||||
|
|
||||||
rag_answer = self._check_and_execute_rag(arguments, formatted_context, citations)
|
rag_answer = self._check_and_execute_rag(arguments, formatted_context, citations)
|
||||||
if rag_answer:
|
if rag_answer:
|
||||||
@@ -202,6 +198,9 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
else:
|
else:
|
||||||
answer = rag_answer.answer
|
answer = rag_answer.answer
|
||||||
|
|
||||||
|
self.flow.state.current_ko_criterium = current_ko_criterium
|
||||||
|
self.flow.state.current_ko_criterium_idx = current_ko_criterium_idx
|
||||||
|
self.flow.state.ko_criteria_scores = []
|
||||||
self.flow.state.answer = answer
|
self.flow.state.answer = answer
|
||||||
self.flow.state.phase = "ko_question_evaluation"
|
self.flow.state.phase = "ko_question_evaluation"
|
||||||
self.flow.state.form_request = ko_form
|
self.flow.state.form_request = ko_form
|
||||||
@@ -219,21 +218,40 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
raise EveAISpecialistExecutionError(self.tenant_id, self.specialist_id, self.session_id,
|
raise EveAISpecialistExecutionError(self.tenant_id, self.specialist_id, self.session_id,
|
||||||
"No form values returned")
|
"No form values returned")
|
||||||
|
|
||||||
|
ko_questions = self._get_ko_questions()
|
||||||
|
|
||||||
|
# DEBUG CHECKS: Valideer het type van het resultaat
|
||||||
|
current_app.logger.debug(f"KO Questions result type: {type(ko_questions)}")
|
||||||
|
current_app.logger.debug(f"Is KOQuestions instance: {isinstance(ko_questions, KOQuestions)}")
|
||||||
|
current_app.logger.debug(f"KO Questions model dump: {ko_questions.model_dump()}")
|
||||||
|
current_app.logger.debug(
|
||||||
|
f"Number of ko_questions: {len(ko_questions.ko_questions) if hasattr(ko_questions, 'ko_questions') else 'No ko_questions attribute'}")
|
||||||
|
|
||||||
|
# Extra check: valideer elk item in de lijst
|
||||||
|
if hasattr(ko_questions, 'ko_questions') and ko_questions.ko_questions:
|
||||||
|
current_app.logger.debug(f"First question type: {type(ko_questions.ko_questions[0])}")
|
||||||
|
current_app.logger.debug(
|
||||||
|
f"First question is KOQuestion: {isinstance(ko_questions.ko_questions[0], KOQuestion)}")
|
||||||
|
current_app.logger.debug(
|
||||||
|
f"First question data: {ko_questions.ko_questions[0].model_dump() if hasattr(ko_questions.ko_questions[0], 'model_dump') else ko_questions.ko_questions[0]}")
|
||||||
|
|
||||||
|
previous_idx = self.flow.state.current_ko_criterium_idx
|
||||||
# Load the previous KO Questions
|
# Load the previous KO Questions
|
||||||
previous_ko_questions = self._get_ko_questions().ko_questions
|
previous_ko_question = ko_questions.ko_questions[previous_idx]
|
||||||
|
|
||||||
# Evaluate KO Criteria
|
# Evaluate KO Criteria
|
||||||
evaluation = "positive"
|
evaluation = "positive"
|
||||||
for criterium, answer in arguments.form_values.items():
|
criterium, answer = next(iter(arguments.form_values.items()))
|
||||||
for qa in previous_ko_questions:
|
if TranslationServices.translate(self.tenant_id, previous_ko_question.answer_positive, arguments.language) != answer:
|
||||||
if qa.title == criterium:
|
evaluation = "negative"
|
||||||
if TranslationServices.translate(self.tenant_id, qa.answer_positive, arguments.language) != answer:
|
|
||||||
evaluation = "negative"
|
|
||||||
break
|
|
||||||
if evaluation == "negative":
|
|
||||||
break
|
|
||||||
|
|
||||||
self.flow.state.ko_criteria_answers = arguments.form_values
|
score = SelectionKOCriteriumScore(
|
||||||
|
criterium=criterium,
|
||||||
|
answer=answer,
|
||||||
|
score=1 if evaluation == "positive" else 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.flow.state.ko_criteria_scores.append(score)
|
||||||
|
|
||||||
if evaluation == "negative":
|
if evaluation == "negative":
|
||||||
answer = TranslationServices.translate(self.tenant_id, KO_CRITERIA_NOT_MET_MESSAGE, arguments.language)
|
answer = TranslationServices.translate(self.tenant_id, KO_CRITERIA_NOT_MET_MESSAGE, arguments.language)
|
||||||
@@ -243,16 +261,35 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
|
|
||||||
results = SelectionResult.create_for_type(self.type, self.type_version)
|
results = SelectionResult.create_for_type(self.type, self.type_version)
|
||||||
else:
|
else:
|
||||||
answer = TranslationServices.translate(self.tenant_id, KO_CRITERIA_MET_MESSAGE, arguments.language)
|
|
||||||
rag_output = self._check_and_execute_rag(arguments, formatted_context, citations)
|
rag_output = self._check_and_execute_rag(arguments, formatted_context, citations)
|
||||||
if rag_output:
|
next_idx = previous_idx + 1
|
||||||
answer = f"{answer}\n\n{rag_output.answer}"
|
|
||||||
answer = (f"{answer}\n\n"
|
|
||||||
f"{TranslationServices.translate(self.tenant_id, RQC_MESSAGE, arguments.language)} "
|
|
||||||
f"{TranslationServices.translate(self.tenant_id, CONTACT_DATA_QUESTION, arguments.language)}")
|
|
||||||
|
|
||||||
self.flow.state.answer = answer
|
if next_idx < len(ko_questions.ko_questions): # There's still a KO criterium to be evaluated
|
||||||
self.flow.state.phase = "personal_contact_data_preparation"
|
next_ko_criterium = ko_questions.ko_questions[next_idx]
|
||||||
|
ko_form = self._prepare_ko_question_form(ko_questions, next_ko_criterium.title, arguments.language)
|
||||||
|
next_message = random.choice(KO_CRITERIA_NEXT_MESSAGES)
|
||||||
|
answer = TranslationServices.translate(self.tenant_id, next_message, arguments.language)
|
||||||
|
if rag_output:
|
||||||
|
answer = f"{rag_output.answer}\n\n{answer}"
|
||||||
|
|
||||||
|
self.flow.state.answer = answer
|
||||||
|
self.flow.state.form_request = ko_form
|
||||||
|
self.flow.state.current_ko_criterium = next_ko_criterium.title
|
||||||
|
self.flow.state.current_ko_criterium_idx = next_idx
|
||||||
|
self.flow.state.phase = "ko_question_evaluation"
|
||||||
|
else: # All KO Criteria have been met
|
||||||
|
answer = TranslationServices.translate(self.tenant_id, KO_CRITERIA_MET_MESSAGE, arguments.language)
|
||||||
|
rag_output = self._check_and_execute_rag(arguments, formatted_context, citations)
|
||||||
|
if rag_output:
|
||||||
|
answer = f"{answer}\n\n{rag_output.answer}"
|
||||||
|
answer = (f"{answer}\n\n"
|
||||||
|
f"{TranslationServices.translate(self.tenant_id, RQC_MESSAGE, arguments.language)} \n\n"
|
||||||
|
f"{TranslationServices.translate(self.tenant_id, CONTACT_DATA_QUESTION, arguments.language)}")
|
||||||
|
|
||||||
|
self.flow.state.answer = answer
|
||||||
|
self.flow.state.current_ko_criterium = ""
|
||||||
|
self.flow.state.current_ko_criterium_idx = None
|
||||||
|
self.flow.state.phase = "personal_contact_data_preparation"
|
||||||
|
|
||||||
results = SelectionResult.create_for_type(self.type, self.type_version,)
|
results = SelectionResult.create_for_type(self.type, self.type_version,)
|
||||||
|
|
||||||
@@ -310,7 +347,7 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
self.flow.state.form_request = time_pref_form
|
self.flow.state.form_request = time_pref_form
|
||||||
|
|
||||||
rqc_info = {
|
rqc_info = {
|
||||||
"ko_criteria_answers": self.flow.state.ko_criteria_answers,
|
"ko_criteria_scores": self.flow.state.ko_criteria_scores,
|
||||||
"personal_contact_data": self.flow.state.personal_contact_data,
|
"personal_contact_data": self.flow.state.personal_contact_data,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +373,7 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
self.flow.state.contact_time_prefs = arguments.form_values
|
self.flow.state.contact_time_prefs = arguments.form_values
|
||||||
|
|
||||||
rqc_info = {
|
rqc_info = {
|
||||||
"ko_criteria_answers": self.flow.state.ko_criteria_answers,
|
"ko_criteria_scores": self.flow.state.ko_criteria_scores,
|
||||||
"personal_contact_data": self.flow.state.personal_contact_data,
|
"personal_contact_data": self.flow.state.personal_contact_data,
|
||||||
"contact_time_prefs": self.flow.state.contact_time_prefs,
|
"contact_time_prefs": self.flow.state.contact_time_prefs,
|
||||||
}
|
}
|
||||||
@@ -476,8 +513,53 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
ko_questions_asset.object_name)
|
ko_questions_asset.object_name)
|
||||||
ko_questions = KOQuestions.from_json(ko_questions_data)
|
ko_questions = KOQuestions.from_json(ko_questions_data)
|
||||||
|
|
||||||
|
# DEBUG CHECKS: Valideer het type van ko_questions_data
|
||||||
|
current_app.logger.debug(f"KO Questions raw data type: {type(ko_questions_data)}")
|
||||||
|
current_app.logger.debug(
|
||||||
|
f"KO Questions raw data content: {ko_questions_data[:200] if isinstance(ko_questions_data, str) else 'Not a string'}")
|
||||||
|
|
||||||
|
# DEBUG CHECKS: Valideer het type van het resultaat
|
||||||
|
current_app.logger.debug(f"KO Questions result type: {type(ko_questions)}")
|
||||||
|
current_app.logger.debug(f"Is KOQuestions instance: {isinstance(ko_questions, KOQuestions)}")
|
||||||
|
current_app.logger.debug(f"KO Questions model dump: {ko_questions.model_dump()}")
|
||||||
|
current_app.logger.debug(
|
||||||
|
f"Number of ko_questions: {len(ko_questions.ko_questions) if hasattr(ko_questions, 'ko_questions') else 'No ko_questions attribute'}")
|
||||||
|
|
||||||
|
# Extra check: valideer elk item in de lijst
|
||||||
|
if hasattr(ko_questions, 'ko_questions') and ko_questions.ko_questions:
|
||||||
|
current_app.logger.debug(f"First question type: {type(ko_questions.ko_questions[0])}")
|
||||||
|
current_app.logger.debug(
|
||||||
|
f"First question is KOQuestion: {isinstance(ko_questions.ko_questions[0], KOQuestion)}")
|
||||||
|
current_app.logger.debug(
|
||||||
|
f"First question data: {ko_questions.ko_questions[0].model_dump() if hasattr(ko_questions.ko_questions[0], 'model_dump') else ko_questions.ko_questions[0]}")
|
||||||
|
|
||||||
return ko_questions
|
return ko_questions
|
||||||
|
|
||||||
|
def _prepare_ko_question_form(self, ko_questions: KOQuestions, current_ko_criterium: str, language: str) \
|
||||||
|
-> Dict[str, Any]:
|
||||||
|
fields = {}
|
||||||
|
ko_question = ko_questions.get_by_title(current_ko_criterium)
|
||||||
|
fields[ko_question.title] = {
|
||||||
|
"name": ko_question.title,
|
||||||
|
"description": ko_question.title,
|
||||||
|
"context": ko_question.question,
|
||||||
|
"type": "options",
|
||||||
|
"required": True,
|
||||||
|
"allowed_values": [ko_question.answer_positive, ko_question.answer_negative]
|
||||||
|
}
|
||||||
|
|
||||||
|
ko_form = {
|
||||||
|
"type": "KO_CRITERIA_FORM",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"name": f"Starter Question: {current_ko_criterium}",
|
||||||
|
"icon": "verified",
|
||||||
|
"fields": fields,
|
||||||
|
}
|
||||||
|
|
||||||
|
ko_form = TranslationServices.translate_config(self.tenant_id, ko_form, "fields", language)
|
||||||
|
|
||||||
|
return ko_form
|
||||||
|
|
||||||
|
|
||||||
class SelectionKOCriteriumScore(BaseModel):
|
class SelectionKOCriteriumScore(BaseModel):
|
||||||
criterium: Optional[str] = Field(None, alias="criterium")
|
criterium: Optional[str] = Field(None, alias="criterium")
|
||||||
@@ -529,7 +611,9 @@ class SelectionFlowState(EveAIFlowState):
|
|||||||
"""Flow state for RAG specialist that automatically updates from task outputs"""
|
"""Flow state for RAG specialist that automatically updates from task outputs"""
|
||||||
input: Optional[SelectionInput] = None
|
input: Optional[SelectionInput] = None
|
||||||
rag_output: Optional[RAGOutput] = None
|
rag_output: Optional[RAGOutput] = None
|
||||||
ko_criteria_answers: Optional[Dict[str, str]] = None
|
current_ko_criterium: Optional[str] = None
|
||||||
|
current_ko_criterium_idx: Optional[int] = None
|
||||||
|
ko_criteria_scores: Optional[List[SelectionKOCriteriumScore]] = None
|
||||||
personal_contact_data: Optional[PersonalContactData] = None
|
personal_contact_data: Optional[PersonalContactData] = None
|
||||||
contact_time_prefs: Optional[ContactTimePreferences] = None
|
contact_time_prefs: Optional[ContactTimePreferences] = None
|
||||||
citations: Optional[List[Dict[str, Any]]] = None
|
citations: Optional[List[Dict[str, Any]]] = None
|
||||||
@@ -537,7 +621,7 @@ class SelectionFlowState(EveAIFlowState):
|
|||||||
|
|
||||||
class SelectionResult(SpecialistResult):
|
class SelectionResult(SpecialistResult):
|
||||||
rag_output: Optional[RAGOutput] = Field(None, alias="rag_output")
|
rag_output: Optional[RAGOutput] = Field(None, alias="rag_output")
|
||||||
ko_criteria_answers: Optional[Dict[str, str]] = Field(None, alias="ko_criteria_answers")
|
ko_criteria_scores: Optional[List[SelectionKOCriteriumScore]] = Field(None, alias="ko_criteria_scores")
|
||||||
personal_contact_data: Optional[PersonalContactData] = Field(None, alias="personal_contact_data")
|
personal_contact_data: Optional[PersonalContactData] = Field(None, alias="personal_contact_data")
|
||||||
contact_time_prefs: Optional[ContactTimePreferences] = None
|
contact_time_prefs: Optional[ContactTimePreferences] = None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user