- RQC output of TRAICIE_SELECTION_SPECIALIST to EveAIDataCapsule

This commit is contained in:
Josako
2025-07-25 04:27:19 +02:00
parent 8a85b4540f
commit ba523a95c5
11 changed files with 338 additions and 5 deletions

View File

@@ -94,6 +94,24 @@ class EveAIAsset(db.Model):
last_used_at = db.Column(db.DateTime, nullable=True)
class EveAIDataCapsule(db.Model):
id = db.Column(db.Integer, primary_key=True)
chat_session_id = db.Column(db.Integer, db.ForeignKey(ChatSession.id), nullable=False)
type = db.Column(db.String(50), nullable=False, default="STANDARD_RAG")
type_version = db.Column(db.String(20), nullable=True, default="1.0.0")
configuration = db.Column(JSONB, nullable=True)
data = db.Column(JSONB, nullable=True)
# Versioning Information
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
created_by = db.Column(db.Integer, db.ForeignKey(User.id), nullable=True)
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
updated_by = db.Column(db.Integer, db.ForeignKey(User.id))
# Unieke constraint voor chat_session_id, type en type_version
__table_args__ = (db.UniqueConstraint('chat_session_id', 'type', 'type_version', name='uix_data_capsule_session_type_version'),)
class EveAIAgent(db.Model):
id = db.Column(db.Integer, primary_key=True)
specialist_id = db.Column(db.Integer, db.ForeignKey(Specialist.id), nullable=False)

View File

@@ -0,0 +1,25 @@
from datetime import datetime as dt, timezone as tz
from common.models.interaction import EveAIDataCapsule
from common.extensions import db
from common.utils.model_logging_utils import set_logging_information, update_logging_information
class CapsuleServices:
@staticmethod
def push_capsule_data(chat_session_id: str, type: str, type_version: str, configuration: dict, data: dict):
capsule = EveAIDataCapsule.query.filter_by(chat_session_id=chat_session_id, type=type, type_version=type_version).first()
if capsule:
# Update bestaande capsule als deze al bestaat
capsule.configuration = configuration
capsule.data = data
update_logging_information(capsule, dt.now(tz.utc))
else:
# Maak nieuwe capsule aan als deze nog niet bestaat
capsule = EveAIDataCapsule(chat_session_id=chat_session_id, type=type, type_version=type_version,
configuration=configuration, data=data)
set_logging_information(capsule, dt.now(tz.utc))
db.session.add(capsule)
db.session.commit()
return capsule

View File

@@ -7,7 +7,7 @@ from flask import current_app
from common.utils.cache.base import CacheHandler, CacheKey
from config.type_defs import agent_types, task_types, tool_types, specialist_types, retriever_types, prompt_types, \
catalog_types, partner_service_types, processor_types, customisation_types, specialist_form_types
catalog_types, partner_service_types, processor_types, customisation_types, specialist_form_types, capsule_types
def is_major_minor(version: str) -> bool:
@@ -485,6 +485,15 @@ SpecialistFormConfigCacheHandler, SpecialistFormConfigVersionTreeCacheHandler, S
)
CapsuleConfigCacheHandler, CapsuleConfigVersionTreeCacheHandler, CapsuleConfigTypesCacheHandler = (
create_config_cache_handlers(
config_type='data_capsules',
config_dir='config/data_capsules',
types_module=capsule_types.CAPSULE_TYPES
)
)
def register_config_cache_handlers(cache_manager) -> None:
cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config')
cache_manager.register_handler(AgentConfigTypesCacheHandler, 'eveai_config')

View File

@@ -0,0 +1,8 @@
version: "1.0.0"
name: "RQC"
description: "Recruitment Qualified Candidate"
configuration: {}
metadata:
author: "Josako"
date_added: "2025-07-24"
description: "Capsule storing RQC information"

View File

@@ -0,0 +1,8 @@
# Catalog Types
CAPSULE_TYPES = {
"TRAICIE_RQC": {
"name": "Traicie Recruitment Qualified Candidate Capsule",
"description": "A capsule storing RQCs",
"partner": "traicie"
},
}

View File

@@ -0,0 +1,111 @@
{% extends "base.html" %}
{% block title %}View Data Capsule{% endblock %}
{% block content_title %}Data Capsule Details{% endblock %}
{% block content_description %}View information about this EvAI Data Capsule and its content.{% endblock %}
{% block content %}
<div class="container mt-5">
<!-- Basic Information Section -->
<h4 class="mb-3">Basis Informatie</h4>
<table class="table table-bordered">
<tr>
<th>ID</th>
<td>{{ data_capsule.id }}</td>
</tr>
<tr>
<th>Type</th>
<td>{{ data_capsule.type }}</td>
</tr>
<tr>
<th>Type Version</th>
<td>{{ data_capsule.type_version }}</td>
</tr>
<tr>
<th>Created At</th>
<td>{{ data_capsule.created_at }}</td>
</tr>
<tr>
<th>Chat Session ID</th>
<td>
<a href="{{ url_for('interaction_bp.view_chat_session', chat_session_id=data_capsule.chat_session_id) }}">
{{ data_capsule.chat_session_id }}
</a>
</td>
</tr>
</table>
<!-- Chat Session Information Section -->
<h4 class="mb-3 mt-4">Chat Sessie Informatie</h4>
<table class="table table-bordered">
<tr>
<th>Session ID</th>
<td>{{ chat_session.session_id }}</td>
</tr>
<tr>
<th>Session Start</th>
<td>{{ chat_session.session_start }}</td>
</tr>
<tr>
<th>Session End</th>
<td>{{ chat_session.session_end }}</td>
</tr>
</table>
<!-- Configuration Data Section -->
{% if data_capsule.configuration %}
<h4 class="mb-3 mt-4">Configuratie</h4>
<div id="configuration-viewer" class="json-viewer" style="height: 300px; width: 100%; border: 1px solid #dee2e6;"></div>
<div id="configuration-viewer-data" class="d-none">{{ data_capsule.configuration | tojson(indent=2) }}</div>
{% endif %}
<!-- Capsule Data Section -->
{% if data_capsule.data %}
<h4 class="mb-3 mt-4">Data</h4>
<div id="data-viewer" class="json-viewer" style="height: 300px; width: 100%; border: 1px solid #dee2e6;"></div>
<div id="data-viewer-data" class="d-none">{{ data_capsule.data | tojson(indent=2) }}</div>
{% endif %}
<!-- Navigation Buttons -->
<div class="mt-5 mb-4">
<a href="{{ url_for('interaction_bp.eveai_data_capsules') }}" class="btn btn-primary">
<i class="fas fa-arrow-left"></i> Back to Data Capsules
</a>
<a href="{{ url_for('interaction_bp.view_chat_session', chat_session_id=data_capsule.chat_session_id) }}" class="btn btn-secondary">
<i class="fas fa-comments"></i> View Chat Session
</a>
</div>
</div>
<!-- Initialize JSON Editor for the data fields -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Configuration JSON Editor if available
if (document.getElementById('configuration-viewer')) {
const configurationData = JSON.parse(document.getElementById('configuration-viewer-data').textContent);
const configurationViewer = new JSONEditor(document.getElementById('configuration-viewer'), {
mode: 'view',
mainMenuBar: false,
navigationBar: false,
statusBar: false,
readOnly: true,
onChangeJSON: function() { /* read only */ }
}, configurationData);
}
// Initialize Data JSON Editor if available
if (document.getElementById('data-viewer')) {
const capsuleData = JSON.parse(document.getElementById('data-viewer-data').textContent);
const dataViewer = new JSONEditor(document.getElementById('data-viewer'), {
mode: 'view',
mainMenuBar: false,
navigationBar: false,
statusBar: false,
readOnly: true,
onChangeJSON: function() { /* read only */ }
}, capsuleData);
}
});
</script>
{% endblock %}

View File

@@ -109,6 +109,7 @@
{'name': 'Specialists', 'url': '/interaction/specialists', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Specialist Magic Links', 'url': '/interaction/specialist_magic_links', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Assets', 'url': '/interaction/assets', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Data Capsules', 'url': '/interaction//eveai_data_capsules', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Chat Sessions', 'url': '/interaction/chat_sessions', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
]) }}
{% endif %}

View File

@@ -15,7 +15,7 @@ from werkzeug.utils import secure_filename
from common.models.document import Embedding, DocumentVersion, Retriever
from common.models.interaction import (ChatSession, Interaction, InteractionEmbedding, Specialist, SpecialistRetriever,
EveAIAgent, EveAITask, EveAITool, EveAIAsset, SpecialistMagicLink)
EveAIAgent, EveAITask, EveAITool, EveAIAsset, SpecialistMagicLink, EveAIDataCapsule)
from common.extensions import db, cache_manager
from common.models.user import SpecialistMagicLinkTenant
@@ -32,7 +32,8 @@ from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAge
SpecialistMagicLinkForm, EditSpecialistMagicLinkForm)
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,
get_eveai_data_capsules_list_view)
from eveai_app.views.list_views.list_view_utils import render_list_view
interaction_bp = Blueprint('interaction_bp', __name__, url_prefix='/interaction')
@@ -747,12 +748,42 @@ def handle_specialist_magic_link_selection():
# Routes for Asset Management ---------------------------------------------------------------------
@interaction_bp.route('/eveai_data_capsules', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def eveai_data_capsules():
# Get configuration and render the list view
config = get_eveai_data_capsules_list_view()
return render_list_view('list_view.html', **config)
@interaction_bp.route('/assets', methods=['GET', 'POST'])
def assets():
config = get_assets_list_view()
return render_list_view('list_view.html', **config)
@interaction_bp.route('/handle_data_capsule_selection', methods=['POST'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def handle_data_capsule_selection():
data_capsule_identification = request.form['selected_row']
capsule_id = ast.literal_eval(data_capsule_identification).get('value')
action = request.form['action']
# Get the data capsule
data_capsule = EveAIDataCapsule.query.get_or_404(capsule_id)
match action:
case 'view_data_capsule':
# For now, we'll just redirect to view_data_capsule
return redirect(prefixed_url_for('interaction_bp.view_data_capsule', data_capsule_id=capsule_id))
case 'view_chat_session':
# Redirect to the chat session
return redirect(prefixed_url_for('interaction_bp.view_chat_session', chat_session_id=data_capsule.chat_session_id))
# Default redirect back to the data capsules list
return redirect(prefixed_url_for('interaction_bp.eveai_data_capsules'))
@interaction_bp.route('/handle_asset_selection', methods=['POST'])
def handle_asset_selection():
action = request.form.get('action')
@@ -765,6 +796,20 @@ def handle_asset_selection():
return redirect(prefixed_url_for('interaction_bp.assets'))
@interaction_bp.route('/view_data_capsule/<int:data_capsule_id>', methods=['GET'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def view_data_capsule(data_capsule_id):
# Get the data capsule
data_capsule = EveAIDataCapsule.query.get_or_404(data_capsule_id)
# Get the related chat session
chat_session = ChatSession.query.get_or_404(data_capsule.chat_session_id)
return render_template('interaction/view_data_capsule.html',
data_capsule=data_capsule,
chat_session=chat_session)
@interaction_bp.route('/edit_asset/<int:asset_id>', methods=['GET', 'POST'])
def edit_asset(asset_id):
asset = EveAIAsset.query.get_or_404(asset_id)

View File

@@ -4,7 +4,7 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import desc
import ast
from common.models.interaction import Specialist, SpecialistMagicLink, ChatSession
from common.models.interaction import Specialist, SpecialistMagicLink, ChatSession, EveAIDataCapsule
from common.utils.nginx_utils import prefixed_url_for
from eveai_app.views.list_views.list_view_utils import render_list_view
@@ -196,3 +196,51 @@ def get_chat_sessions_list_view():
'table_height': 800
}
def get_eveai_data_capsules_list_view():
"""Generate the EvAI Data Capsules list view configuration"""
# Get all data capsules ordered by creation date (descending)
data_capsules_query = EveAIDataCapsule.query.order_by(desc(EveAIDataCapsule.created_at))
all_data_capsules = data_capsules_query.all()
# Prepare data for Tabulator
data = []
for capsule in all_data_capsules:
data.append({
'id': capsule.id,
'chat_session_id': capsule.chat_session_id,
'type': capsule.type,
'type_version': capsule.type_version,
'created_at': capsule.created_at.strftime('%Y-%m-%d %H:%M:%S') if capsule.created_at else ''
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Chat Session ID', 'field': 'chat_session_id'},
{'title': 'Type', 'field': 'type'},
{'title': 'Type Version', 'field': 'type_version'},
{'title': 'Created At', 'field': 'created_at'}
]
# Action definitions
actions = [
{'value': 'view_data_capsule', 'text': 'View Details', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'view_chat_session', 'text': 'View Chat Session', 'class': 'btn-secondary', 'requiresSelection': True}
]
# Initial sort configuration
initial_sort = [{'column': 'created_at', 'dir': 'desc'}]
return {
'title': 'EvAI Data Capsules',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'eveai_data_capsules_table',
'form_action': url_for('interaction_bp.handle_data_capsule_selection'),
'description': 'View all EvAI Data Capsules',
'table_height': 800
}

View File

@@ -9,6 +9,7 @@ from pydantic import BaseModel, Field, EmailStr
from common.extensions import cache_manager, db, minio_client
from common.models.interaction import EveAIAsset
from common.models.user import Tenant
from common.services.interaction.capsule_services import CapsuleServices
from common.services.utils.human_answer_services import HumanAnswerServices
from common.services.utils.translation_services import TranslationServices
from common.utils.business_event_context import current_event
@@ -308,6 +309,13 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
self.flow.state.personal_contact_data = arguments.form_values
self.flow.state.form_request = time_pref_form
rqc_info = {
"ko_criteria_answers": self.flow.state.ko_criteria_answers,
"personal_contact_data": self.flow.state.personal_contact_data,
}
CapsuleServices.push_capsule_data(self._cached_session.id, "TRAICIE_RQC", "1.0", {}, rqc_info)
results = SelectionResult.create_for_type(self.type, self.type_version,)
return results
@@ -327,8 +335,15 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
current_app.logger.debug(f"Contact time evaluation: {arguments.form_values}")
self.flow.state.contact_time_prefs = arguments.form_values
rqc_info = {
"ko_criteria_answers": self.flow.state.ko_criteria_answers,
"personal_contact_data": self.flow.state.personal_contact_data,
"contact_time_prefs": self.flow.state.contact_time_prefs,
}
CapsuleServices.push_capsule_data(self._cached_session.id, "TRAICIE_RQC", "1.0", {}, rqc_info)
results = SelectionResult.create_for_type(self.type, self.type_version,)
current_app.logger.debug(f"Results: {results.model_dump()}")
return results

View File

@@ -0,0 +1,45 @@
"""Introduction of EveAIDataCapsule model
Revision ID: 5e3dd539e5c1
Revises: 26e8f0d8c143
Create Date: 2025-07-24 13:31:46.238128
"""
from alembic import op
import sqlalchemy as sa
import pgvector
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '5e3dd539e5c1'
down_revision = '26e8f0d8c143'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('eve_ai_data_capsule',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('chat_session_id', sa.Integer(), nullable=False),
sa.Column('type', sa.String(length=50), nullable=False),
sa.Column('type_version', sa.String(length=20), nullable=True),
sa.Column('configuration', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('data', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('created_by', sa.Integer(), nullable=True),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_by', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['chat_session_id'], ['chat_session.id'], ),
sa.ForeignKeyConstraint(['created_by'], ['public.user.id'], ),
sa.ForeignKeyConstraint(['updated_by'], ['public.user.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('chat_session_id', 'type', 'type_version', name='uix_data_capsule_session_type_version')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('eve_ai_data_capsule')
# ### end Alembic commands ###